cli_boxes/lib.rs
1//! # CLI Boxes
2//!
3//! [](https://crates.io/crates/cli-boxes)
4//! [](https://docs.rs/cli-boxes)
5//! [](https://github.com/sabry-awad97/boxen-rs)
6//!
7//! A high-performance Rust library providing Unicode box-drawing characters for creating beautiful CLI interfaces.
8//! Perfect for terminal applications, CLI tools, and text-based user interfaces.
9//!
10//! ## 🎯 Features
11//!
12//! - **9 Beautiful Box Styles**: From elegant single-line to decorative arrows
13//! - **Unicode & ASCII Support**: Full Unicode compliance with ASCII fallback
14//! - **Zero-Cost Abstractions**: Compile-time constants and zero-allocation parsing
15//! - **Type Safety**: Strong typing prevents incorrect usage
16//! - **Ergonomic Builder API**: Fluent interface for custom box creation
17//! - **String Parsing**: Parse styles from configuration files or user input
18//! - **Serde Integration**: Optional serialization support (feature-gated)
19//! - **Comprehensive Testing**: 100% test coverage with extensive edge case handling
20//!
21//! ## 📦 Box Styles
22//!
23//! | Style | Preview | Use Case |
24//! |-------|---------|----------|
25//! | `SINGLE` | `┌─┐│┘─└│` | General purpose, clean appearance |
26//! | `DOUBLE` | `╔═╗║╝═╚║` | Emphasis, important content |
27//! | `ROUND` | `╭─╮│╯─╰│` | Modern, soft appearance |
28//! | `BOLD` | `┏━┓┃┛━┗┃` | Strong emphasis, headers |
29//! | `SINGLE_DOUBLE` | `╓─╖║╜─╙║` | Mixed style, unique look |
30//! | `DOUBLE_SINGLE` | `╒═╕│╛═╘│` | Alternative mixed style |
31//! | `CLASSIC` | `+─+|+─+|` | ASCII compatibility, legacy systems |
32//! | `ARROW` | `↘↓↙←↖↑↗→` | Decorative, special effects |
33//! | `NONE` | ` ` | Invisible borders, spacing |
34//!
35//! ## 🚀 Quick Start
36//!
37//! Add to your `Cargo.toml`:
38//! ```toml
39//! [dependencies]
40//! cli-boxes = "0.1.0"
41//! ```
42//!
43//! ### Basic Usage
44//!
45//! ```rust
46//! use cli_boxes::{BoxChars, BorderStyle};
47//!
48//! // Create a simple box
49//! let box_chars = BoxChars::SINGLE;
50//! println!("{}{}{}",
51//! box_chars.top_left,
52//! box_chars.top.to_string().repeat(10),
53//! box_chars.top_right
54//! );
55//! println!("{} {}", box_chars.left, box_chars.right);
56//! println!("{}{}{}",
57//! box_chars.bottom_left,
58//! box_chars.bottom.to_string().repeat(10),
59//! box_chars.bottom_right
60//! );
61//! // Output:
62//! // ┌──────────┐
63//! // │ │
64//! // └──────────┘
65//! ```
66//!
67//! ### Advanced Usage with Builder Pattern
68//!
69//! ```rust
70//! use cli_boxes::BoxChars;
71//!
72//! // Create custom box with builder pattern
73//! let custom = BoxChars::builder()
74//! .corners('●')
75//! .horizontal('═')
76//! .vertical('║')
77//! .top_left('╔') // Override specific corner
78//! .build();
79//!
80//! // Asymmetric design
81//! let fancy = BoxChars::builder()
82//! .top_left('╭').top('─').top_right('╮')
83//! .left('│').right('│')
84//! .bottom_left('└').bottom('─').bottom_right('┘')
85//! .build();
86//! ```
87//!
88//! ### Dynamic Style Selection
89//!
90//! ```rust
91//! use cli_boxes::{BorderStyle, BoxChars};
92//!
93//! // Parse from configuration
94//! let style: BorderStyle = "double".parse().unwrap();
95//! let box_chars = style.chars();
96//!
97//! // Iterate through all styles
98//! for style in BorderStyle::all() {
99//! let chars = style.chars();
100//! println!("{}: {}", style, chars);
101//! }
102//! ```
103//!
104//! ## 🎨 Real-World Examples
105//!
106//! ### Creating a Status Box
107//!
108//! ```rust
109//! use cli_boxes::BoxChars;
110//!
111//! fn create_status_box(message: &str, width: usize) -> String {
112//! let box_chars = BoxChars::DOUBLE;
113//! let padding = width.saturating_sub(message.len() + 2);
114//! let left_pad = padding / 2;
115//! let right_pad = padding - left_pad;
116//!
117//! format!(
118//! "{}{}{}\n{}{}{}{}{}\n{}{}{}",
119//! box_chars.top_left,
120//! box_chars.top.to_string().repeat(width),
121//! box_chars.top_right,
122//! box_chars.left,
123//! " ".repeat(left_pad),
124//! message,
125//! " ".repeat(right_pad),
126//! box_chars.right,
127//! box_chars.bottom_left,
128//! box_chars.bottom.to_string().repeat(width),
129//! box_chars.bottom_right
130//! )
131//! }
132//! ```
133//!
134//! ### Configuration-Driven Boxes
135//!
136//! ```rust
137//! use cli_boxes::{BorderStyle, BoxChars};
138//! use std::collections::HashMap;
139//!
140//! fn get_box_for_level(level: &str) -> BoxChars {
141//! let styles: HashMap<&str, BorderStyle> = [
142//! ("info", BorderStyle::Single),
143//! ("warning", BorderStyle::Bold),
144//! ("error", BorderStyle::Double),
145//! ("success", BorderStyle::Round),
146//! ].iter().cloned().collect();
147//!
148//! styles.get(level)
149//! .map(|s| s.chars())
150//! .unwrap_or(BoxChars::CLASSIC)
151//! }
152//! ```
153//!
154//! ## ⚡ Performance
155//!
156//! This library is designed for maximum performance:
157//!
158//! - **Zero Allocations**: String parsing uses byte-level comparison without heap allocation
159//! - **Compile-Time Constants**: All predefined styles are `const` and computed at compile time
160//! - **Minimal Dependencies**: Only `strum` for enum iteration, optional `serde` for serialization
161//! - **Small Memory Footprint**: `BoxChars` is only 32 bytes (8 × 4-byte chars)
162//!
163//! ## 🔧 Optional Features
164//!
165//! ### Serde Support
166//!
167//! Enable serialization support:
168//! ```toml
169//! [dependencies]
170//! cli-boxes = { version = "0.1.0", features = ["serde"] }
171//! ```
172//!
173//! ```rust
174//! # #[cfg(feature = "serde")]
175//! # {
176//! use cli_boxes::{BoxChars, BorderStyle};
177//! use serde_json;
178//!
179//! let style = BorderStyle::Double;
180//! let json = serde_json::to_string(&style).unwrap();
181//! let deserialized: BorderStyle = serde_json::from_str(&json).unwrap();
182//! # }
183//! ```
184//!
185//! ## 🛡️ Error Handling
186//!
187//! The library provides helpful error messages for invalid input:
188//!
189//! ```rust
190//! use cli_boxes::BorderStyle;
191//!
192//! match "invalid_style".parse::<BorderStyle>() {
193//! Ok(style) => println!("Parsed: {}", style),
194//! Err(e) => println!("{}", e),
195//! // Output: Invalid border style: 'invalid_style'.
196//! // Did you mean one of: none, single, double, round, bold,
197//! // single_double, double_single, classic, arrow?
198//! }
199//! ```
200//!
201//! ## 🔗 Related Crates
202//!
203//! - [`tui`](https://crates.io/crates/tui) - Terminal user interface library
204//! - [`crossterm`](https://crates.io/crates/crossterm) - Cross-platform terminal manipulation
205//! - [`console`](https://crates.io/crates/console) - Terminal and console abstraction
206//!
207//! ## 📄 License
208//!
209//! This project is licensed under either of
210//! - Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE))
211//! - MIT License ([LICENSE-MIT](LICENSE-MIT))
212//!
213//! at your option.
214
215#![cfg_attr(docsrs, feature(doc_cfg))]
216#![deny(missing_docs)]
217#![warn(clippy::all)]
218#![warn(clippy::pedantic)]
219#![warn(clippy::nursery)]
220#![warn(clippy::cargo)]
221#![doc(html_root_url = "https://docs.rs/cli-boxes/0.1.0")]
222#![doc(
223 html_logo_url = "https://raw.githubusercontent.com/sabry-awad97/boxen-rs/main/assets/logo.png"
224)]
225#![doc(
226 html_favicon_url = "https://raw.githubusercontent.com/sabry-awad97/boxen-rs/main/assets/favicon.ico"
227)]
228
229#[cfg(feature = "serde")]
230use serde::{Deserialize, Serialize};
231use std::fmt;
232use std::str::FromStr;
233use strum::{EnumIter, IntoEnumIterator};
234
235/// A collection of Unicode characters used for drawing boxes in CLI applications.
236///
237/// This struct contains all the necessary characters to draw a complete box:
238/// corners, horizontal lines, and vertical lines. Each field represents a specific
239/// position in the box structure.
240///
241/// # Box Structure
242///
243/// ```text
244/// top_left ──── top ──── top_right
245/// │ │
246/// left right
247/// │ │
248/// bottom_left ── bottom ── bottom_right
249/// ```
250///
251/// # Memory Layout
252///
253/// `BoxChars` is a compact struct containing 8 `char` values (32 bytes total on most platforms).
254/// All predefined constants are evaluated at compile time for zero runtime cost.
255///
256/// # Thread Safety
257///
258/// `BoxChars` implements `Send` and `Sync`, making it safe to use across threads.
259/// All operations are immutable after construction.
260///
261/// # Examples
262///
263/// ## Using Predefined Styles
264///
265/// ```rust
266/// use cli_boxes::BoxChars;
267///
268/// // Use predefined single-line box characters
269/// let single = BoxChars::SINGLE;
270/// assert_eq!(single.top_left, '┌');
271/// assert_eq!(single.top, '─');
272/// assert_eq!(single.top_right, '┐');
273///
274/// // Double-line for emphasis
275/// let double = BoxChars::DOUBLE;
276/// assert_eq!(double.top_left, '╔');
277/// ```
278///
279/// ## Custom Box Creation
280///
281/// ```rust
282/// use cli_boxes::BoxChars;
283///
284/// // Direct constructor
285/// let custom = BoxChars::new('*', '-', '*', '|', '*', '-', '*', '|');
286///
287/// // Builder pattern (recommended for readability)
288/// let builder_box = BoxChars::builder()
289/// .corners('*')
290/// .horizontal('-')
291/// .vertical('|')
292/// .build();
293///
294/// assert_eq!(custom, builder_box);
295/// ```
296///
297/// ## Drawing a Complete Box
298///
299/// ```rust
300/// use cli_boxes::BoxChars;
301///
302/// fn draw_box(chars: &BoxChars, width: usize, height: usize) -> String {
303/// let mut result = String::new();
304///
305/// // Top border
306/// result.push(chars.top_left);
307/// result.push_str(&chars.top.to_string().repeat(width.saturating_sub(2)));
308/// result.push(chars.top_right);
309/// result.push('\n');
310///
311/// // Side borders
312/// for _ in 0..height.saturating_sub(2) {
313/// result.push(chars.left);
314/// result.push_str(&" ".repeat(width.saturating_sub(2)));
315/// result.push(chars.right);
316/// result.push('\n');
317/// }
318///
319/// // Bottom border
320/// result.push(chars.bottom_left);
321/// result.push_str(&chars.bottom.to_string().repeat(width.saturating_sub(2)));
322/// result.push(chars.bottom_right);
323///
324/// result
325/// }
326///
327/// let box_str = draw_box(&BoxChars::SINGLE, 5, 3);
328/// println!("{}", box_str);
329/// // Output:
330/// // ┌───┐
331/// // │ │
332/// // └───┘
333/// ```
334#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
335#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
336#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
337pub struct BoxChars {
338 /// Top-left corner character
339 pub top_left: char,
340 /// Top border character (repeated horizontally)
341 pub top: char,
342 /// Top-right corner character
343 pub top_right: char,
344 /// Right border character (repeated vertically)
345 pub right: char,
346 /// Bottom-right corner character
347 pub bottom_right: char,
348 /// Bottom border character (repeated horizontally)
349 pub bottom: char,
350 /// Bottom-left corner character
351 pub bottom_left: char,
352 /// Left border character (repeated vertically)
353 pub left: char,
354}
355
356/// Macro to reduce boilerplate when defining box character constants.
357macro_rules! box_chars {
358 ($name:ident, $tl:literal, $t:literal, $tr:literal, $r:literal, $br:literal, $b:literal, $bl:literal, $l:literal, $doc:literal) => {
359 #[doc = $doc]
360 pub const $name: Self = Self {
361 top_left: $tl,
362 top: $t,
363 top_right: $tr,
364 right: $r,
365 bottom_right: $br,
366 bottom: $b,
367 bottom_left: $bl,
368 left: $l,
369 };
370 };
371}
372
373impl BoxChars {
374 /// Creates a new `BoxChars` with the specified characters.
375 ///
376 /// # Arguments
377 ///
378 /// * `top_left` - Top-left corner character
379 /// * `top` - Top border character
380 /// * `top_right` - Top-right corner character
381 /// * `right` - Right border character
382 /// * `bottom_right` - Bottom-right corner character
383 /// * `bottom` - Bottom border character
384 /// * `bottom_left` - Bottom-left corner character
385 /// * `left` - Left border character
386 ///
387 /// # Examples
388 ///
389 /// ```rust
390 /// use cli_boxes::BoxChars;
391 ///
392 /// let custom = BoxChars::new('*', '-', '*', '|', '*', '-', '*', '|');
393 /// assert_eq!(custom.top_left, '*');
394 /// assert_eq!(custom.top, '-');
395 /// ```
396 #[must_use]
397 #[allow(clippy::too_many_arguments)]
398 pub const fn new(
399 top_left: char,
400 top: char,
401 top_right: char,
402 right: char,
403 bottom_right: char,
404 bottom: char,
405 bottom_left: char,
406 left: char,
407 ) -> Self {
408 Self {
409 top_left,
410 top,
411 top_right,
412 right,
413 bottom_right,
414 bottom,
415 bottom_left,
416 left,
417 }
418 }
419
420 /// Creates a builder for constructing custom box characters.
421 ///
422 /// # Examples
423 ///
424 /// ```rust
425 /// use cli_boxes::BoxChars;
426 ///
427 /// let custom = BoxChars::builder()
428 /// .corners('*')
429 /// .horizontal('-')
430 /// .vertical('|')
431 /// .build();
432 /// ```
433 #[must_use]
434 pub fn builder() -> BoxCharsBuilder {
435 BoxCharsBuilder::default()
436 }
437
438 box_chars!(
439 NONE,
440 ' ',
441 ' ',
442 ' ',
443 ' ',
444 ' ',
445 ' ',
446 ' ',
447 ' ',
448 "Creates a box with no visible characters (all spaces).\n\nThis is useful when you want to maintain box structure without visible borders, or as a fallback when no box styling is desired."
449 );
450
451 box_chars!(
452 SINGLE,
453 '┌',
454 '─',
455 '┐',
456 '│',
457 '┘',
458 '─',
459 '└',
460 '│',
461 "Creates a box using single-line Unicode box-drawing characters.\n\nThis is the most commonly used box style, providing clean and professional-looking borders that work well in most terminal environments.\n\nCharacters used: `┌─┐│┘─└│`"
462 );
463
464 box_chars!(
465 DOUBLE,
466 '╔',
467 '═',
468 '╗',
469 '║',
470 '╝',
471 '═',
472 '╚',
473 '║',
474 "Creates a box using double-line Unicode box-drawing characters.\n\nThis style provides a bold, prominent appearance that's excellent for highlighting important content or creating emphasis in CLI applications.\n\nCharacters used: `╔═╗║╝═╚║`"
475 );
476
477 box_chars!(
478 ROUND,
479 '╭',
480 '─',
481 '╮',
482 '│',
483 '╯',
484 '─',
485 '╰',
486 '│',
487 "Creates a box using rounded corner Unicode box-drawing characters.\n\nThis style provides a softer, more modern appearance with curved corners that's aesthetically pleasing and less harsh than sharp corners.\n\nCharacters used: `╭─╮│╯─╰│`"
488 );
489
490 box_chars!(
491 BOLD,
492 '┏',
493 '━',
494 '┓',
495 '┃',
496 '┛',
497 '━',
498 '┗',
499 '┃',
500 "Creates a box using bold/thick line Unicode box-drawing characters.\n\nThis style provides maximum visual impact with thick, bold lines that command attention and create strong visual separation.\n\nCharacters used: `┏━┓┃┛━┗┃`"
501 );
502
503 box_chars!(
504 SINGLE_DOUBLE,
505 '╓',
506 '─',
507 '╖',
508 '║',
509 '╜',
510 '─',
511 '╙',
512 '║',
513 "Creates a box using single horizontal, double vertical box-drawing characters.\n\nThis mixed style combines single-line horizontal borders with double-line vertical borders, creating a unique visual effect.\n\nCharacters used: `╓─╖║╜─╙║`"
514 );
515
516 box_chars!(
517 DOUBLE_SINGLE,
518 '╒',
519 '═',
520 '╕',
521 '│',
522 '╛',
523 '═',
524 '╘',
525 '│',
526 "Creates a box using double horizontal, single vertical box-drawing characters.\n\nThis mixed style combines double-line horizontal borders with single-line vertical borders, creating an alternative visual effect to `SINGLE_DOUBLE`.\n\nCharacters used: `╒═╕│╛═╘│`"
527 );
528
529 box_chars!(
530 CLASSIC,
531 '+',
532 '-',
533 '+',
534 '|',
535 '+',
536 '-',
537 '+',
538 '|',
539 "Creates a box using classic ASCII characters for maximum compatibility.\n\nThis style uses only basic ASCII characters, ensuring compatibility with all terminal environments, including those that don't support Unicode.\n\nCharacters used: `+-+|+-+|`"
540 );
541
542 box_chars!(
543 ARROW,
544 '↘',
545 '↓',
546 '↙',
547 '←',
548 '↖',
549 '↑',
550 '↗',
551 '→',
552 "Creates a decorative box using arrow Unicode characters.\n\nThis unique style uses directional arrows to create an unconventional but eye-catching border effect. Best used for special emphasis or creative CLI applications.\n\nCharacters used: `↘↓↙←↖↑↗→`"
553 );
554}
555
556impl Default for BoxChars {
557 /// Returns the default box characters (single-line style).
558 ///
559 /// # Examples
560 ///
561 /// ```rust
562 /// use cli_boxes::BoxChars;
563 ///
564 /// let default_box = BoxChars::default();
565 /// assert_eq!(default_box, BoxChars::SINGLE);
566 /// ```
567 fn default() -> Self {
568 Self::SINGLE
569 }
570}
571
572impl fmt::Display for BoxChars {
573 /// Formats the box characters as a string showing all 8 characters in order.
574 ///
575 /// The format is: `top_left`, `top`, `top_right`, `right`, `bottom_right`, `bottom`, `bottom_left`, `left`
576 ///
577 /// # Examples
578 ///
579 /// ```rust
580 /// use cli_boxes::BoxChars;
581 ///
582 /// let single = BoxChars::SINGLE;
583 /// println!("{}", single); // Outputs: ┌─┐│┘─└│
584 /// ```
585 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
586 write!(
587 f,
588 "{}{}{}{}{}{}{}{}",
589 self.top_left,
590 self.top,
591 self.top_right,
592 self.right,
593 self.bottom_right,
594 self.bottom,
595 self.bottom_left,
596 self.left
597 )
598 }
599}
600
601/// Builder for creating custom `BoxChars` with a fluent, type-safe API.
602///
603/// The builder pattern provides an ergonomic way to construct custom box characters
604/// with method chaining. All methods are `const fn` where possible for compile-time
605/// evaluation. The builder starts with invisible characters (`NONE`) and allows
606/// selective customization.
607///
608/// # Design Patterns
609///
610/// ## Uniform Styling
611/// Use the convenience methods for consistent appearance:
612/// - `corners()` - Sets all four corners to the same character
613/// - `horizontal()` - Sets top and bottom borders
614/// - `vertical()` - Sets left and right borders
615///
616/// ## Selective Overrides
617/// Start with uniform styling, then override specific positions:
618///
619/// ```rust
620/// use cli_boxes::BoxChars;
621///
622/// let mixed = BoxChars::builder()
623/// .corners('●') // Set all corners
624/// .horizontal('═') // Set horizontal borders
625/// .vertical('║') // Set vertical borders
626/// .top_left('╔') // Override just top-left
627/// .build();
628/// ```
629///
630/// ## Asymmetric Designs
631/// Create unique, asymmetric box styles:
632///
633/// ```rust
634/// use cli_boxes::BoxChars;
635///
636/// let asymmetric = BoxChars::builder()
637/// .top_left('╭').top('─').top_right('╮')
638/// .left('│').right('│')
639/// .bottom_left('└').bottom('─').bottom_right('┘')
640/// .build();
641/// ```
642///
643/// ## Theme-Based Construction
644///
645/// ```rust
646/// use cli_boxes::BoxChars;
647///
648/// fn create_theme_box(theme: &str) -> BoxChars {
649/// let mut builder = BoxChars::builder();
650///
651/// match theme {
652/// "minimal" => builder.corners('+').horizontal('-').vertical('|'),
653/// "fancy" => builder.corners('●').horizontal('═').vertical('║'),
654/// "retro" => builder.corners('*').horizontal('=').vertical(':'),
655/// _ => builder.corners('?').horizontal('?').vertical('?'),
656/// }.build()
657/// }
658/// ```
659///
660/// # Performance
661///
662/// The builder uses `const fn` methods where possible, allowing compile-time
663/// evaluation when used with constant inputs. The final `build()` call is
664/// zero-cost, simply returning the constructed `BoxChars` struct.
665#[derive(Debug, Clone)]
666pub struct BoxCharsBuilder {
667 chars: BoxChars,
668}
669
670impl Default for BoxCharsBuilder {
671 fn default() -> Self {
672 Self {
673 chars: BoxChars::NONE,
674 }
675 }
676}
677
678impl BoxCharsBuilder {
679 /// Sets all corner characters to the same value.
680 #[must_use]
681 pub const fn corners(mut self, char: char) -> Self {
682 self.chars.top_left = char;
683 self.chars.top_right = char;
684 self.chars.bottom_left = char;
685 self.chars.bottom_right = char;
686 self
687 }
688
689 /// Sets both horizontal border characters (top and bottom) to the same value.
690 #[must_use]
691 pub const fn horizontal(mut self, char: char) -> Self {
692 self.chars.top = char;
693 self.chars.bottom = char;
694 self
695 }
696
697 /// Sets both vertical border characters (left and right) to the same value.
698 #[must_use]
699 pub const fn vertical(mut self, char: char) -> Self {
700 self.chars.left = char;
701 self.chars.right = char;
702 self
703 }
704
705 /// Sets the top-left corner character.
706 #[must_use]
707 pub const fn top_left(mut self, char: char) -> Self {
708 self.chars.top_left = char;
709 self
710 }
711
712 /// Sets the top border character.
713 #[must_use]
714 pub const fn top(mut self, char: char) -> Self {
715 self.chars.top = char;
716 self
717 }
718
719 /// Sets the top-right corner character.
720 #[must_use]
721 pub const fn top_right(mut self, char: char) -> Self {
722 self.chars.top_right = char;
723 self
724 }
725
726 /// Sets the right border character.
727 #[must_use]
728 pub const fn right(mut self, char: char) -> Self {
729 self.chars.right = char;
730 self
731 }
732
733 /// Sets the bottom-right corner character.
734 #[must_use]
735 pub const fn bottom_right(mut self, char: char) -> Self {
736 self.chars.bottom_right = char;
737 self
738 }
739
740 /// Sets the bottom border character.
741 #[must_use]
742 pub const fn bottom(mut self, char: char) -> Self {
743 self.chars.bottom = char;
744 self
745 }
746
747 /// Sets the bottom-left corner character.
748 #[must_use]
749 pub const fn bottom_left(mut self, char: char) -> Self {
750 self.chars.bottom_left = char;
751 self
752 }
753
754 /// Sets the left border character.
755 #[must_use]
756 pub const fn left(mut self, char: char) -> Self {
757 self.chars.left = char;
758 self
759 }
760
761 /// Builds and returns the final `BoxChars`.
762 #[must_use]
763 pub const fn build(self) -> BoxChars {
764 self.chars
765 }
766}
767
768/// Available box drawing styles with semantic meaning and use cases.
769///
770/// Each style provides a different visual appearance optimized for specific use cases.
771/// This enum provides a convenient, type-safe way to select box styles without
772/// directly referencing character constants.
773///
774/// # Style Guidelines
775///
776/// - **Single**: Default choice for most applications, clean and readable
777/// - **Double**: Use for emphasis, headers, or important content sections
778/// - **Round**: Modern appearance, good for user-friendly interfaces
779/// - **Bold**: Maximum emphasis, warnings, or critical information
780/// - **Mixed Styles**: Unique visual effects, decorative purposes
781/// - **Classic**: ASCII-only environments, legacy system compatibility
782/// - **Arrow**: Special effects, directional indicators, creative designs
783/// - **None**: Invisible spacing, layout without visible borders
784///
785/// # Performance Notes
786///
787/// All enum variants are zero-cost abstractions that compile to direct character constants.
788/// String parsing is optimized for performance with zero heap allocations.
789///
790/// # Examples
791///
792/// ## Basic Usage
793///
794/// ```rust
795/// use cli_boxes::{BorderStyle, BoxChars};
796///
797/// // Convert enum to box characters
798/// let box_chars = BoxChars::from(BorderStyle::Single);
799/// assert_eq!(box_chars.top_left, '┌');
800///
801/// // Using the convenience method
802/// let double_chars = BorderStyle::Double.chars();
803/// assert_eq!(double_chars.top_left, '╔');
804/// ```
805///
806/// ## Dynamic Style Selection
807///
808/// ```rust
809/// use cli_boxes::BorderStyle;
810///
811/// fn get_style_for_severity(level: u8) -> BorderStyle {
812/// match level {
813/// 0 => BorderStyle::None,
814/// 1 => BorderStyle::Single,
815/// 2 => BorderStyle::Bold,
816/// 3 => BorderStyle::Double,
817/// _ => BorderStyle::Classic,
818/// }
819/// }
820///
821/// let style = get_style_for_severity(2);
822/// let chars = style.chars();
823/// ```
824///
825/// ## Iteration and Discovery
826///
827/// ```rust
828/// use cli_boxes::BorderStyle;
829///
830/// // Display all available styles
831/// for style in BorderStyle::all() {
832/// let chars = style.chars();
833/// println!("{:12} -> {}", style.to_string(), chars);
834/// }
835/// ```
836///
837/// ## String Parsing with Error Handling
838///
839/// ```rust
840/// use cli_boxes::BorderStyle;
841/// use std::str::FromStr;
842///
843/// // Parse from configuration files or user input
844/// let styles = ["single", "double", "round", "invalid"];
845///
846/// for style_str in &styles {
847/// match style_str.parse::<BorderStyle>() {
848/// Ok(style) => println!("✓ Parsed '{}' as {:?}", style_str, style),
849/// Err(e) => println!("✗ Error: {}", e),
850/// }
851/// }
852/// ```
853#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, EnumIter)]
854pub enum BorderStyle {
855 /// No box characters (all spaces)
856 None,
857 /// Single-line box drawing characters: ┌─┐│┘─└│
858 Single,
859 /// Double-line box drawing characters: ╔═╗║╝═╚║
860 Double,
861 /// Rounded corner box drawing characters: ╭─╮│╯─╰│
862 Round,
863 /// Bold/thick line box drawing characters: ┏━┓┃┛━┗┃
864 Bold,
865 /// Single horizontal, double vertical: ╓─╖║╜─╙║
866 SingleDouble,
867 /// Double horizontal, single vertical: ╒═╕│╛═╘│
868 DoubleSingle,
869 /// ASCII-compatible classic box characters: +─+|+─+|
870 Classic,
871 /// Arrow-based decorative box characters: ↘↓↙←↖↑↗→
872 Arrow,
873}
874
875impl From<BorderStyle> for BoxChars {
876 /// Converts a `BorderStyle` enum variant to its corresponding `BoxChars`.
877 ///
878 /// # Examples
879 ///
880 /// ```rust
881 /// use cli_boxes::{BorderStyle, BoxChars};
882 ///
883 /// let single_chars = BoxChars::from(BorderStyle::Single);
884 /// assert_eq!(single_chars, BoxChars::SINGLE);
885 ///
886 /// let double_chars = BoxChars::from(BorderStyle::Double);
887 /// assert_eq!(double_chars, BoxChars::DOUBLE);
888 /// ```
889 fn from(style: BorderStyle) -> Self {
890 match style {
891 BorderStyle::None => Self::NONE,
892 BorderStyle::Single => Self::SINGLE,
893 BorderStyle::Double => Self::DOUBLE,
894 BorderStyle::Round => Self::ROUND,
895 BorderStyle::Bold => Self::BOLD,
896 BorderStyle::SingleDouble => Self::SINGLE_DOUBLE,
897 BorderStyle::DoubleSingle => Self::DOUBLE_SINGLE,
898 BorderStyle::Classic => Self::CLASSIC,
899 BorderStyle::Arrow => Self::ARROW,
900 }
901 }
902}
903
904impl BorderStyle {
905 /// Returns the `BoxChars` associated with this border style.
906 ///
907 /// This is a convenience method that's equivalent to `BoxChars::from(style)`.
908 ///
909 /// # Examples
910 ///
911 /// ```rust
912 /// use cli_boxes::BorderStyle;
913 ///
914 /// let style = BorderStyle::Bold;
915 /// let chars = style.chars();
916 /// assert_eq!(chars.top, '━');
917 /// ```
918 #[must_use]
919 pub fn chars(self) -> BoxChars {
920 BoxChars::from(self)
921 }
922
923 /// Returns an iterator over all available border styles.
924 ///
925 /// This is a convenience method that uses the `EnumIter` trait.
926 ///
927 /// # Examples
928 ///
929 /// ```rust
930 /// use cli_boxes::BorderStyle;
931 ///
932 /// for style in BorderStyle::all() {
933 /// println!("Style: {:?}", style);
934 /// }
935 /// ```
936 pub fn all() -> impl Iterator<Item = Self> {
937 Self::iter()
938 }
939}
940
941impl fmt::Display for BorderStyle {
942 /// Formats the border style as a lowercase string.
943 ///
944 /// # Examples
945 ///
946 /// ```rust
947 /// use cli_boxes::BorderStyle;
948 ///
949 /// assert_eq!(BorderStyle::Single.to_string(), "single");
950 /// assert_eq!(BorderStyle::DoubleSingle.to_string(), "double_single");
951 /// assert_eq!(BorderStyle::Arrow.to_string(), "arrow");
952 /// ```
953 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
954 let s = match self {
955 Self::None => "none",
956 Self::Single => "single",
957 Self::Double => "double",
958 Self::Round => "round",
959 Self::Bold => "bold",
960 Self::SingleDouble => "single_double",
961 Self::DoubleSingle => "double_single",
962 Self::Classic => "classic",
963 Self::Arrow => "arrow",
964 };
965 write!(f, "{s}")
966 }
967}
968
969impl FromStr for BorderStyle {
970 type Err = ParseBorderStyleError;
971
972 /// Parses a string into a `BorderStyle`.
973 ///
974 /// The parsing is case-insensitive and accepts both `snake_case` and `kebab-case`.
975 /// This implementation is optimized to avoid heap allocations.
976 ///
977 /// # Examples
978 ///
979 /// ```rust
980 /// use std::str::FromStr;
981 /// use cli_boxes::BorderStyle;
982 ///
983 /// assert_eq!("single".parse::<BorderStyle>().unwrap(), BorderStyle::Single);
984 /// assert_eq!("DOUBLE".parse::<BorderStyle>().unwrap(), BorderStyle::Double);
985 /// assert_eq!("single-double".parse::<BorderStyle>().unwrap(), BorderStyle::SingleDouble);
986 /// assert_eq!("double_single".parse::<BorderStyle>().unwrap(), BorderStyle::DoubleSingle);
987 ///
988 /// // Error case
989 /// assert!("invalid".parse::<BorderStyle>().is_err());
990 /// ```
991 fn from_str(s: &str) -> Result<Self, Self::Err> {
992 // Helper function to match strings case-insensitively with hyphen/underscore normalization
993 let matches = |target: &str| -> bool {
994 if s.len() != target.len() {
995 return false;
996 }
997 s.bytes().zip(target.bytes()).all(|(a, b)| {
998 let a_norm = if a == b'-' {
999 b'_'
1000 } else {
1001 a.to_ascii_lowercase()
1002 };
1003 a_norm == b
1004 })
1005 };
1006
1007 if matches("none") {
1008 Ok(Self::None)
1009 } else if matches("single") {
1010 Ok(Self::Single)
1011 } else if matches("double") {
1012 Ok(Self::Double)
1013 } else if matches("round") {
1014 Ok(Self::Round)
1015 } else if matches("bold") {
1016 Ok(Self::Bold)
1017 } else if matches("single_double") {
1018 Ok(Self::SingleDouble)
1019 } else if matches("double_single") {
1020 Ok(Self::DoubleSingle)
1021 } else if matches("classic") {
1022 Ok(Self::Classic)
1023 } else if matches("arrow") {
1024 Ok(Self::Arrow)
1025 } else {
1026 Err(ParseBorderStyleError::InvalidStyle(s.to_string()))
1027 }
1028 }
1029}
1030
1031/// Error type for parsing `BorderStyle` from string.
1032#[derive(Debug, Clone, PartialEq, Eq)]
1033pub enum ParseBorderStyleError {
1034 /// The provided string does not match any known border style.
1035 InvalidStyle(String),
1036}
1037
1038impl fmt::Display for ParseBorderStyleError {
1039 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1040 match self {
1041 Self::InvalidStyle(style) => {
1042 let suggestions = BorderStyle::all()
1043 .map(|s| s.to_string())
1044 .collect::<Vec<_>>()
1045 .join(", ");
1046
1047 write!(
1048 f,
1049 "Invalid border style: '{style}'. Did you mean one of: {suggestions}?"
1050 )
1051 }
1052 }
1053 }
1054}
1055
1056impl std::error::Error for ParseBorderStyleError {}
1057
1058#[cfg(test)]
1059mod tests {
1060 use super::*;
1061 use std::collections::HashSet;
1062
1063 #[test]
1064 fn test_box_chars_constants() {
1065 // Test that all constants have the expected characters
1066 assert_eq!(BoxChars::NONE.top_left, ' ');
1067 assert_eq!(BoxChars::SINGLE.top_left, '┌');
1068 assert_eq!(BoxChars::DOUBLE.top_left, '╔');
1069 assert_eq!(BoxChars::ROUND.top_left, '╭');
1070 assert_eq!(BoxChars::BOLD.top_left, '┏');
1071 assert_eq!(BoxChars::SINGLE_DOUBLE.top_left, '╓');
1072 assert_eq!(BoxChars::DOUBLE_SINGLE.top_left, '╒');
1073 assert_eq!(BoxChars::CLASSIC.top_left, '+');
1074 assert_eq!(BoxChars::ARROW.top_left, '↘');
1075 }
1076
1077 #[test]
1078 fn test_box_chars_new() {
1079 let custom = BoxChars::new('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h');
1080 assert_eq!(custom.top_left, 'a');
1081 assert_eq!(custom.top, 'b');
1082 assert_eq!(custom.top_right, 'c');
1083 assert_eq!(custom.right, 'd');
1084 assert_eq!(custom.bottom_right, 'e');
1085 assert_eq!(custom.bottom, 'f');
1086 assert_eq!(custom.bottom_left, 'g');
1087 assert_eq!(custom.left, 'h');
1088 }
1089
1090 #[test]
1091 fn test_box_chars_default() {
1092 let default = BoxChars::default();
1093 assert_eq!(default, BoxChars::SINGLE);
1094 }
1095
1096 #[test]
1097 fn test_box_chars_display() {
1098 let single = BoxChars::SINGLE;
1099 assert_eq!(single.to_string(), "┌─┐│┘─└│");
1100
1101 let custom = BoxChars::new('*', '-', '*', '|', '*', '-', '*', '|');
1102 assert_eq!(custom.to_string(), "*-*|*-*|");
1103 }
1104
1105 #[test]
1106 fn test_box_chars_traits() {
1107 let single = BoxChars::SINGLE;
1108 let single_copy = single;
1109
1110 // Test Copy trait
1111 assert_eq!(single, single_copy);
1112
1113 // Test Hash trait
1114 let mut set = HashSet::new();
1115 set.insert(single);
1116 assert!(set.contains(&single));
1117
1118 // Test Debug trait
1119 let debug_str = format!("{single:?}");
1120 assert!(debug_str.contains("BoxChars"));
1121 }
1122
1123 #[test]
1124 fn test_builder_pattern() {
1125 let custom = BoxChars::builder()
1126 .corners('*')
1127 .horizontal('-')
1128 .vertical('|')
1129 .build();
1130
1131 assert_eq!(custom.top_left, '*');
1132 assert_eq!(custom.top_right, '*');
1133 assert_eq!(custom.bottom_left, '*');
1134 assert_eq!(custom.bottom_right, '*');
1135 assert_eq!(custom.top, '-');
1136 assert_eq!(custom.bottom, '-');
1137 assert_eq!(custom.left, '|');
1138 assert_eq!(custom.right, '|');
1139 }
1140
1141 #[test]
1142 fn test_builder_individual_methods() {
1143 let custom = BoxChars::builder()
1144 .top_left('a')
1145 .top('b')
1146 .top_right('c')
1147 .right('d')
1148 .bottom_right('e')
1149 .bottom('f')
1150 .bottom_left('g')
1151 .left('h')
1152 .build();
1153
1154 assert_eq!(custom.top_left, 'a');
1155 assert_eq!(custom.top, 'b');
1156 assert_eq!(custom.top_right, 'c');
1157 assert_eq!(custom.right, 'd');
1158 assert_eq!(custom.bottom_right, 'e');
1159 assert_eq!(custom.bottom, 'f');
1160 assert_eq!(custom.bottom_left, 'g');
1161 assert_eq!(custom.left, 'h');
1162 }
1163
1164 #[test]
1165 fn test_builder_chaining() {
1166 let result = BoxChars::builder()
1167 .corners('●')
1168 .horizontal('═')
1169 .vertical('║')
1170 .top_left('╔') // Override corner
1171 .build();
1172
1173 assert_eq!(result.top_left, '╔'); // Overridden
1174 assert_eq!(result.top_right, '●'); // From corners
1175 assert_eq!(result.top, '═'); // From horizontal
1176 assert_eq!(result.left, '║'); // From vertical
1177 }
1178
1179 #[test]
1180 fn test_builder_default() {
1181 let default_builder = BoxCharsBuilder::default();
1182 let result = default_builder.build();
1183 assert_eq!(result, BoxChars::NONE);
1184 }
1185
1186 #[test]
1187 fn test_border_style_enum() {
1188 // Test all variants exist
1189 let styles = [
1190 BorderStyle::None,
1191 BorderStyle::Single,
1192 BorderStyle::Double,
1193 BorderStyle::Round,
1194 BorderStyle::Bold,
1195 BorderStyle::SingleDouble,
1196 BorderStyle::DoubleSingle,
1197 BorderStyle::Classic,
1198 BorderStyle::Arrow,
1199 ];
1200
1201 assert_eq!(styles.len(), 9);
1202 }
1203
1204 #[test]
1205 fn test_border_style_to_box_chars() {
1206 assert_eq!(BoxChars::from(BorderStyle::None), BoxChars::NONE);
1207 assert_eq!(BoxChars::from(BorderStyle::Single), BoxChars::SINGLE);
1208 assert_eq!(BoxChars::from(BorderStyle::Double), BoxChars::DOUBLE);
1209 assert_eq!(BoxChars::from(BorderStyle::Round), BoxChars::ROUND);
1210 assert_eq!(BoxChars::from(BorderStyle::Bold), BoxChars::BOLD);
1211 assert_eq!(
1212 BoxChars::from(BorderStyle::SingleDouble),
1213 BoxChars::SINGLE_DOUBLE
1214 );
1215 assert_eq!(
1216 BoxChars::from(BorderStyle::DoubleSingle),
1217 BoxChars::DOUBLE_SINGLE
1218 );
1219 assert_eq!(BoxChars::from(BorderStyle::Classic), BoxChars::CLASSIC);
1220 assert_eq!(BoxChars::from(BorderStyle::Arrow), BoxChars::ARROW);
1221 }
1222
1223 #[test]
1224 fn test_border_style_chars_method() {
1225 let style = BorderStyle::Bold;
1226 let chars = style.chars();
1227 assert_eq!(chars, BoxChars::BOLD);
1228 assert_eq!(chars.top, '━');
1229 }
1230
1231 #[test]
1232 fn test_border_style_display() {
1233 assert_eq!(BorderStyle::None.to_string(), "none");
1234 assert_eq!(BorderStyle::Single.to_string(), "single");
1235 assert_eq!(BorderStyle::Double.to_string(), "double");
1236 assert_eq!(BorderStyle::Round.to_string(), "round");
1237 assert_eq!(BorderStyle::Bold.to_string(), "bold");
1238 assert_eq!(BorderStyle::SingleDouble.to_string(), "single_double");
1239 assert_eq!(BorderStyle::DoubleSingle.to_string(), "double_single");
1240 assert_eq!(BorderStyle::Classic.to_string(), "classic");
1241 assert_eq!(BorderStyle::Arrow.to_string(), "arrow");
1242 }
1243
1244 #[test]
1245 fn test_border_style_from_str() {
1246 // Test basic parsing
1247 assert_eq!("none".parse::<BorderStyle>().unwrap(), BorderStyle::None);
1248 assert_eq!(
1249 "single".parse::<BorderStyle>().unwrap(),
1250 BorderStyle::Single
1251 );
1252 assert_eq!(
1253 "double".parse::<BorderStyle>().unwrap(),
1254 BorderStyle::Double
1255 );
1256 assert_eq!("round".parse::<BorderStyle>().unwrap(), BorderStyle::Round);
1257 assert_eq!("bold".parse::<BorderStyle>().unwrap(), BorderStyle::Bold);
1258 assert_eq!(
1259 "single_double".parse::<BorderStyle>().unwrap(),
1260 BorderStyle::SingleDouble
1261 );
1262 assert_eq!(
1263 "double_single".parse::<BorderStyle>().unwrap(),
1264 BorderStyle::DoubleSingle
1265 );
1266 assert_eq!(
1267 "classic".parse::<BorderStyle>().unwrap(),
1268 BorderStyle::Classic
1269 );
1270 assert_eq!("arrow".parse::<BorderStyle>().unwrap(), BorderStyle::Arrow);
1271 }
1272
1273 #[test]
1274 fn test_border_style_from_conversion() {
1275 assert_eq!(BoxChars::from(BorderStyle::Single), BoxChars::SINGLE);
1276 assert_eq!(BoxChars::from(BorderStyle::Double), BoxChars::DOUBLE);
1277 assert_eq!(BoxChars::from(BorderStyle::Round), BoxChars::ROUND);
1278 assert_eq!(BoxChars::from(BorderStyle::Bold), BoxChars::BOLD);
1279 assert_eq!(
1280 BoxChars::from(BorderStyle::SingleDouble),
1281 BoxChars::SINGLE_DOUBLE
1282 );
1283 assert_eq!(
1284 BoxChars::from(BorderStyle::DoubleSingle),
1285 BoxChars::DOUBLE_SINGLE
1286 );
1287 assert_eq!(BoxChars::from(BorderStyle::Classic), BoxChars::CLASSIC);
1288 assert_eq!(BoxChars::from(BorderStyle::Arrow), BoxChars::ARROW);
1289 assert_eq!(BoxChars::from(BorderStyle::None), BoxChars::NONE);
1290 }
1291
1292 #[test]
1293 fn test_border_style_all() {
1294 let all_styles: Vec<_> = BorderStyle::all().collect();
1295 assert_eq!(all_styles.len(), 9);
1296 assert!(all_styles.contains(&BorderStyle::Single));
1297 assert!(all_styles.contains(&BorderStyle::Double));
1298 }
1299
1300 #[test]
1301 fn test_border_style_from_str_case_insensitive() {
1302 assert_eq!(
1303 "SINGLE".parse::<BorderStyle>().unwrap(),
1304 BorderStyle::Single
1305 );
1306 assert_eq!(
1307 "Double".parse::<BorderStyle>().unwrap(),
1308 BorderStyle::Double
1309 );
1310 assert_eq!("ROUND".parse::<BorderStyle>().unwrap(), BorderStyle::Round);
1311 assert_eq!("Bold".parse::<BorderStyle>().unwrap(), BorderStyle::Bold);
1312 }
1313
1314 #[test]
1315 fn test_border_style_from_str_kebab_case() {
1316 assert_eq!(
1317 "single-double".parse::<BorderStyle>().unwrap(),
1318 BorderStyle::SingleDouble
1319 );
1320 assert_eq!(
1321 "double-single".parse::<BorderStyle>().unwrap(),
1322 BorderStyle::DoubleSingle
1323 );
1324 assert_eq!(
1325 "SINGLE-DOUBLE".parse::<BorderStyle>().unwrap(),
1326 BorderStyle::SingleDouble
1327 );
1328 assert_eq!(
1329 "Double-Single".parse::<BorderStyle>().unwrap(),
1330 BorderStyle::DoubleSingle
1331 );
1332 }
1333
1334 #[test]
1335 fn test_border_style_from_str_invalid() {
1336 assert!("invalid".parse::<BorderStyle>().is_err());
1337 assert!("".parse::<BorderStyle>().is_err());
1338 assert!("singleee".parse::<BorderStyle>().is_err());
1339 assert!("single_".parse::<BorderStyle>().is_err());
1340 }
1341
1342 #[test]
1343 fn test_parse_border_style_error() {
1344 let error = "invalid".parse::<BorderStyle>().unwrap_err();
1345 assert_eq!(
1346 error,
1347 ParseBorderStyleError::InvalidStyle("invalid".to_string())
1348 );
1349
1350 let error_display = error.to_string();
1351 assert!(error_display.contains("Invalid border style: 'invalid'"));
1352 assert!(error_display.contains("Did you mean one of:"));
1353 assert!(error_display.contains("single"));
1354 assert!(error_display.contains("double"));
1355 }
1356
1357 #[test]
1358 fn test_parse_border_style_error_traits() {
1359 let error = ParseBorderStyleError::InvalidStyle("test".to_string());
1360
1361 // Test Debug
1362 let debug_str = format!("{error:?}");
1363 assert!(debug_str.contains("InvalidStyle"));
1364
1365 // Test Error trait
1366 let _: &dyn std::error::Error = &error;
1367 }
1368
1369 #[test]
1370 fn test_zero_allocation_parsing() {
1371 // This test ensures our optimized parsing doesn't allocate
1372 // We can't directly test allocations in unit tests, but we can test
1373 // that the parsing logic works correctly for edge cases
1374
1375 // Test exact length matching
1376 assert!("single".parse::<BorderStyle>().is_ok());
1377 assert!("singlee".parse::<BorderStyle>().is_err()); // Too long
1378 assert!("singl".parse::<BorderStyle>().is_err()); // Too short
1379
1380 // Test case normalization
1381 assert_eq!(
1382 "SINGLE".parse::<BorderStyle>().unwrap(),
1383 BorderStyle::Single
1384 );
1385 assert_eq!(
1386 "single".parse::<BorderStyle>().unwrap(),
1387 BorderStyle::Single
1388 );
1389 assert_eq!(
1390 "Single".parse::<BorderStyle>().unwrap(),
1391 BorderStyle::Single
1392 );
1393
1394 // Test hyphen/underscore normalization
1395 assert_eq!(
1396 "single-double".parse::<BorderStyle>().unwrap(),
1397 BorderStyle::SingleDouble
1398 );
1399 assert_eq!(
1400 "single_double".parse::<BorderStyle>().unwrap(),
1401 BorderStyle::SingleDouble
1402 );
1403 }
1404
1405 #[test]
1406 fn test_comprehensive_style_coverage() {
1407 // Ensure all BorderStyle variants have corresponding BoxChars
1408 for style in BorderStyle::all() {
1409 let chars = BoxChars::from(style);
1410 let chars_method = style.chars();
1411 assert_eq!(chars, chars_method);
1412
1413 // Ensure the conversion is reversible through string representation
1414 let style_str = style.to_string();
1415 let parsed_style: BorderStyle = style_str.parse().unwrap();
1416 assert_eq!(style, parsed_style);
1417 }
1418 }
1419
1420 #[test]
1421 fn test_unicode_characters() {
1422 // Test that Unicode characters are correctly stored and retrieved
1423 let single = BoxChars::SINGLE;
1424 assert_eq!(single.top_left as u32, 0x250C); // ┌
1425 assert_eq!(single.top as u32, 0x2500); // ─
1426
1427 let double = BoxChars::DOUBLE;
1428 assert_eq!(double.top_left as u32, 0x2554); // ╔
1429 assert_eq!(double.top as u32, 0x2550); // ═
1430
1431 let round = BoxChars::ROUND;
1432 assert_eq!(round.top_left as u32, 0x256D); // ╭
1433 assert_eq!(round.bottom_right as u32, 0x256F); // ╯
1434 }
1435
1436 #[cfg(feature = "serde")]
1437 #[test]
1438 fn test_serde_serialization() {
1439 use serde_json;
1440
1441 let single = BoxChars::SINGLE;
1442 let serialized = serde_json::to_string(&single).unwrap();
1443 let deserialized: BoxChars = serde_json::from_str(&serialized).unwrap();
1444 assert_eq!(single, deserialized);
1445
1446 // Test camelCase field names
1447 assert!(serialized.contains("topLeft"));
1448 assert!(serialized.contains("topRight"));
1449 assert!(serialized.contains("bottomLeft"));
1450 assert!(serialized.contains("bottomRight"));
1451 }
1452
1453 #[test]
1454 fn test_edge_cases() {
1455 // Test empty builder
1456 let empty = BoxChars::builder().build();
1457 assert_eq!(empty, BoxChars::NONE);
1458
1459 // Test builder method chaining order doesn't matter
1460 let box1 = BoxChars::builder()
1461 .corners('*')
1462 .horizontal('-')
1463 .vertical('|')
1464 .build();
1465 let box2 = BoxChars::builder()
1466 .vertical('|')
1467 .corners('*')
1468 .horizontal('-')
1469 .build();
1470 assert_eq!(box1, box2);
1471
1472 // Test that builder methods override previous values
1473 let overridden = BoxChars::builder().corners('*').corners('#').build();
1474 assert_eq!(overridden.top_left, '#');
1475 assert_eq!(overridden.top_right, '#');
1476 }
1477
1478 #[test]
1479 fn test_memory_layout() {
1480 use std::mem;
1481
1482 // BoxChars should be small and efficiently packed
1483 assert_eq!(mem::size_of::<BoxChars>(), mem::size_of::<char>() * 8);
1484
1485 // BorderStyle should be small (single byte enum)
1486 assert!(mem::size_of::<BorderStyle>() <= mem::size_of::<u8>());
1487
1488 // Test alignment
1489 assert_eq!(mem::align_of::<BoxChars>(), mem::align_of::<char>());
1490 }
1491}