Skip to main content

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 json;
148pub mod prompt;
149pub mod traceback;
150
151// Optional feature-gated modules
152#[cfg(feature = "markdown")]
153pub mod markdown;
154
155#[cfg(feature = "syntax")]
156pub mod syntax;
157
158// Re-exports for convenience
159pub use console::Console;
160pub use layout::Layout;
161pub use live::Live;
162pub use panel::{BorderStyle, Panel};
163pub use renderable::Renderable;
164pub use rule::Rule;
165pub use style::{Color, Style};
166pub use table::{Column, ColumnAlign, Table};
167pub use text::{Alignment, Text};
168pub use tree::{Tree, TreeNode};
169
170// ============================================================================
171// Thread-local Console for Print Macros
172// ============================================================================
173
174thread_local! {
175    static STDOUT_CONSOLE: RefCell<Console> = RefCell::new(Console::new());
176    static STDERR_CONSOLE: RefCell<Console> = RefCell::new(Console::stderr());
177    // Raw consoles have markup parsing disabled - for data output
178    static STDOUT_RAW_CONSOLE: RefCell<Console> = RefCell::new(Console::new().markup(false));
179    static STDERR_RAW_CONSOLE: RefCell<Console> = RefCell::new(Console::stderr().markup(false));
180}
181
182/// Internal helper for print macros - DO NOT USE DIRECTLY.
183#[doc(hidden)]
184pub fn __internal_print(content: String, newline: bool) {
185    STDOUT_CONSOLE.with(|c| {
186        let console = c.borrow();
187        if newline {
188            console.println(&content);
189        } else {
190            console.print(&content);
191        }
192    });
193}
194
195/// Internal helper for eprint macros - DO NOT USE DIRECTLY.
196#[doc(hidden)]
197pub fn __internal_eprint(content: String, newline: bool) {
198    STDERR_CONSOLE.with(|c| {
199        let console = c.borrow();
200        if newline {
201            console.println(&content);
202        } else {
203            console.print(&content);
204        }
205    });
206}
207
208/// Internal helper for raw print macros (no markup parsing) - DO NOT USE DIRECTLY.
209#[doc(hidden)]
210pub fn __internal_print_raw(content: String, newline: bool) {
211    STDOUT_RAW_CONSOLE.with(|c| {
212        let console = c.borrow();
213        if newline {
214            console.println(&content);
215        } else {
216            console.print(&content);
217        }
218    });
219}
220
221/// Internal helper for raw eprint macros (no markup parsing) - DO NOT USE DIRECTLY.
222#[doc(hidden)]
223pub fn __internal_eprint_raw(content: String, newline: bool) {
224    STDERR_RAW_CONSOLE.with(|c| {
225        let console = c.borrow();
226        if newline {
227            console.println(&content);
228        } else {
229            console.print(&content);
230        }
231    });
232}
233
234// ============================================================================
235// Print Macros - Drop-in replacements for std::print! and std::println!
236// ============================================================================
237
238/// Print formatted text with Rich markup to stdout (no newline).
239///
240/// This macro is a drop-in replacement for `std::print!` that adds
241/// Rich markup support for colors, styles, and formatting.
242///
243/// # Example
244///
245/// ```no_run
246/// use fast_rich::print;
247///
248/// print!("[bold blue]Status:[/] checking... ");
249/// print!("Value: [yellow]{}[/]", 42);
250/// ```
251#[macro_export]
252macro_rules! print {
253    ($($arg:tt)*) => {{
254        $crate::__internal_print(format!($($arg)*), false);
255    }};
256}
257
258/// Print formatted text with Rich markup to stdout (with newline).
259///
260/// This macro is a drop-in replacement for `std::println!` that adds
261/// Rich markup support for colors, styles, and formatting.
262///
263/// # Example
264///
265/// ```no_run
266/// use fast_rich::println;
267///
268/// println!("[bold green]Success![/] All tests passed.");
269/// println!("Player {} scored [yellow]{}[/] points", "Alice", 100);
270/// println!();  // Empty line
271/// ```
272#[macro_export]
273macro_rules! println {
274    () => {{
275        $crate::__internal_print(String::new(), true);
276    }};
277    ($($arg:tt)*) => {{
278        $crate::__internal_print(format!($($arg)*), true);
279    }};
280}
281
282/// Print formatted text with Rich markup to stderr (no newline).
283///
284/// This macro is a drop-in replacement for `std::eprint!` that adds
285/// Rich markup support for colors, styles, and formatting.
286///
287/// # Example
288///
289/// ```no_run
290/// use fast_rich::eprint;
291///
292/// eprint!("[red]Error:[/] ");
293/// ```
294#[macro_export]
295macro_rules! eprint {
296    ($($arg:tt)*) => {{
297        $crate::__internal_eprint(format!($($arg)*), false);
298    }};
299}
300
301/// Print formatted text with Rich markup to stderr (with newline).
302///
303/// This macro is a drop-in replacement for `std::eprintln!` that adds
304/// Rich markup support for colors, styles, and formatting.
305///
306/// # Example
307///
308/// ```no_run
309/// use fast_rich::eprintln;
310///
311/// eprintln!("[bold red]Error:[/] Something went wrong!");
312/// eprintln!("[yellow]Warning:[/] {} items skipped", 5);
313/// ```
314#[macro_export]
315macro_rules! eprintln {
316    () => {{
317        $crate::__internal_eprint(String::new(), true);
318    }};
319    ($($arg:tt)*) => {{
320        $crate::__internal_eprint(format!($($arg)*), true);
321    }};
322}
323
324// ============================================================================
325// Raw Print Macros - No markup parsing, safe for data output
326// ============================================================================
327
328/// Print to stdout without markup parsing (no newline).
329///
330/// Use this for data output that may contain brackets like `[1, 2, 3]`
331/// which would otherwise be interpreted as style tags.
332///
333/// # Example
334///
335/// ```no_run
336/// use fast_rich::print_raw;
337///
338/// let data = vec![1, 2, 3];
339/// print_raw!("Data: {:?}", data);  // Brackets printed literally
340/// ```
341#[macro_export]
342macro_rules! print_raw {
343    ($($arg:tt)*) => {{
344        $crate::__internal_print_raw(format!($($arg)*), false);
345    }};
346}
347
348/// Print to stdout without markup parsing (with newline).
349///
350/// Use this for data output that may contain brackets like `[1, 2, 3]`
351/// which would otherwise be interpreted as style tags.
352///
353/// # Example
354///
355/// ```no_run
356/// use fast_rich::println_raw;
357///
358/// let items = vec!["a", "b", "c"];
359/// println_raw!("Items: {:?}", items);  // Brackets printed literally
360/// ```
361#[macro_export]
362macro_rules! println_raw {
363    () => {{
364        $crate::__internal_print_raw(String::new(), true);
365    }};
366    ($($arg:tt)*) => {{
367        $crate::__internal_print_raw(format!($($arg)*), true);
368    }};
369}
370
371/// Print to stderr without markup parsing (no newline).
372#[macro_export]
373macro_rules! eprint_raw {
374    ($($arg:tt)*) => {{
375        $crate::__internal_eprint_raw(format!($($arg)*), false);
376    }};
377}
378
379/// Print to stderr without markup parsing (with newline).
380#[macro_export]
381macro_rules! eprintln_raw {
382    () => {{
383        $crate::__internal_eprint_raw(String::new(), true);
384    }};
385    ($($arg:tt)*) => {{
386        $crate::__internal_eprint_raw(format!($($arg)*), true);
387    }};
388}
389
390// ============================================================================
391// Aliases - For users who want both standard and rich macros
392// ============================================================================
393
394/// Alias for `print!` - use when you need both `std::print!` and rich print.
395///
396/// # Example
397///
398/// ```no_run
399/// use fast_rich::rprint;
400///
401/// std::print!("Standard: no markup [bold]");
402/// rprint!("Rich: [bold]this is bold[/]");
403/// ```
404#[macro_export]
405macro_rules! rprint {
406    ($($arg:tt)*) => {{
407        $crate::print!($($arg)*);
408    }};
409}
410
411/// Alias for `println!` - use when you need both `std::println!` and rich println.
412///
413/// # Example
414///
415/// ```no_run
416/// use fast_rich::rprintln;
417///
418/// std::println!("Standard: no markup [bold]");
419/// rprintln!("Rich: [bold]this is bold[/]");
420/// ```
421#[macro_export]
422macro_rules! rprintln {
423    () => {{
424        $crate::println!();
425    }};
426    ($($arg:tt)*) => {{
427        $crate::println!($($arg)*);
428    }};
429}
430
431/// Prelude module for convenient imports.
432///
433/// ## Print Macros
434///
435/// The print macros are NOT included in the prelude to avoid conflicts with `std`.
436/// Import them explicitly if you want drop-in shadowing:
437///
438/// ```no_run
439/// use fast_rich::{print, println};
440/// println!("[bold green]Hello![/]");
441/// ```
442///
443/// The following ARE included in the prelude:
444/// - `rprint!` / `rprintln!` - Aliases that don't conflict with std
445/// - `print_raw!` / `println_raw!` - Raw output without markup parsing
446pub mod prelude {
447    // Aliases that don't conflict with std
448    pub use crate::{rprint, rprintln};
449
450    // Raw print macros for data output (no markup parsing, no std conflicts)
451    pub use crate::{eprint_raw, eprintln_raw, print_raw, println_raw};
452
453    pub use crate::columns::Columns;
454    pub use crate::console::Console;
455    pub use crate::inspect::{inspect, InspectConfig};
456    pub use crate::json::Json;
457    pub use crate::log::ConsoleLog;
458    pub use crate::panel::{BorderStyle, Panel};
459    pub use crate::progress::{track, Progress, ProgressBar, Spinner, SpinnerStyle, Status};
460    pub use crate::renderable::Renderable;
461    pub use crate::rule::Rule;
462    pub use crate::style::{Color, Style};
463    pub use crate::table::{Column, ColumnAlign, Table};
464    pub use crate::text::{Alignment, Text};
465    pub use crate::traceback::install_panic_hook;
466    pub use crate::tree::{GuideStyle, Tree, TreeNode};
467}
468
469#[cfg(test)]
470mod tests {
471    use super::*;
472
473    #[test]
474    fn test_console_creation() {
475        let console = Console::new();
476        assert!(console.get_width() > 0);
477    }
478
479    #[test]
480    fn test_style_builder() {
481        let style = Style::new().foreground(Color::Red).bold().underline();
482
483        assert!(style.bold);
484        assert!(style.underline);
485    }
486
487    #[test]
488    fn test_text_creation() {
489        let text = Text::plain("Hello, World!");
490        assert_eq!(text.plain_text(), "Hello, World!");
491    }
492
493    #[test]
494    fn test_table_creation() {
495        let mut table = Table::new();
496        table.add_column("Col1");
497        table.add_column("Col2");
498        table.add_row_strs(&["a", "b"]);
499
500        // Table should have columns and rows
501        assert!(!table
502            .render(&console::RenderContext {
503                width: 40,
504                height: None
505            })
506            .is_empty());
507    }
508}