clp/lib.rs
1//! # clp – Command Line Printer
2//!
3//! `clp` is a simple utility crate for printing formatted, bordered text blocks
4//! in the terminal using `#` characters.
5//!
6//! It is useful for CLI tools that want to display structured output such as
7//! titles, body text, and boxed sections in a readable and visually distinct way.
8//!
9//! ## Features
10//!
11//! - Print a title surrounded by `#` borders
12//! - Print word-wrapped body text with `#` side borders
13//! - Print a `#` border line
14//! - Combine all of the above into a single full "hashtag box"
15//!
16//! ## Quick Start
17//!
18//! ```rust
19//! clprint::print_hashtag_box("My Title", "This is the body text of my box.", 40);
20//! ```
21//!
22//! Output:
23//! ```text
24//! ########################################
25//! # My Title #
26//! ########################################
27//! # This is the body text of my box. #
28//! ########################################
29//! ```
30//!
31//! ## Usage
32//!
33//! Add `clp` to your `Cargo.toml`:
34//!
35//! ```toml
36//! [dependencies]
37//! clp = "0.1.0"
38//! ```
39//!
40//! Then import and use in your project:
41//!
42//! ```rust
43//! use clprint::{print_hashtag_box, print_hashtag_title, print_hashtag_text, print_hashtag_line};
44//!
45//! fn main() {
46//! print_hashtag_box("Welcome", "Hello from clp!", 40);
47//! }
48//! ```
49
50/// Prints a title block surrounded by `#` border lines.
51///
52/// The title is displayed between two full-width `#` lines, padded to fill
53/// the given `width`.
54///
55/// # Arguments
56///
57/// * `title` - The title text to display. Should be shorter than `width - 3`
58/// to avoid overflow.
59/// * `width` - The total width of the box in characters, including the borders.
60///
61/// # Examples
62///
63/// ```rust
64/// clprint::print_hashtag_title("My Title", 40);
65/// ```
66///
67/// Output:
68/// ```text
69/// ########################################
70/// # My Title #
71/// ########################################
72/// ```
73///
74/// # Panics
75///
76/// Panics if `width` is less than 3, as the padding calculation would underflow.
77pub fn print_hashtag_title(title: &str, width: usize) {
78 println!("{}", "#".repeat(width));
79 println!("# {:text_width$}#", title, text_width = width - 3);
80 println!("{}", "#".repeat(width));
81}
82
83/// Prints body text with `#` side borders, with automatic word wrapping.
84///
85/// The text is split by newlines first, then each line is word-wrapped to fit
86/// within the available width. Each printed line is padded to ensure the
87/// right-side `#` border stays aligned.
88///
89/// # Arguments
90///
91/// * `text` - The text to display. Supports `\n` for manual line breaks.
92/// * `width` - The total width of the box in characters, including the borders.
93/// The usable text area is `width - 4` (accounting for `# ` and ` #`).
94///
95/// # Examples
96///
97/// Basic usage:
98/// ```rust
99/// clprint::print_hashtag_text("Hello World", 20);
100/// ```
101///
102/// Output:
103/// ```text
104/// # Hello World #
105/// ```
106///
107/// With word wrapping:
108/// ```rust
109/// clprint::print_hashtag_text("This is a longer text that will be wrapped", 20);
110/// ```
111///
112/// Output:
113/// ```text
114/// # This is a longer #
115/// # text that will #
116/// # be wrapped #
117/// ```
118///
119/// With manual line breaks:
120/// ```rust
121/// clprint::print_hashtag_text("Line one\nLine two", 20);
122/// ```
123///
124/// # Panics
125///
126/// Panics if `width` is less than 4, as the chunk size calculation would underflow.
127pub fn print_hashtag_text(text: &str, width: usize) {
128 let chunk_size = width - 4;
129 for row in text.trim().split('\n') {
130 let words = row.split(" ");
131 let mut printline = String::new();
132 for word in words {
133 if printline.len() + word.len() + 1 < chunk_size {
134 printline.push_str(word);
135 printline.push(' ');
136 } else {
137 println!("# {:text_width$} #", printline, text_width = chunk_size);
138 printline.clear();
139 printline.push_str(word);
140 printline.push(' ');
141 }
142 }
143 if !printline.is_empty() {
144 println!(
145 "# {:text_width$} #",
146 printline.trim_end(),
147 text_width = chunk_size
148 );
149 }
150 }
151}
152
153/// Prints a single `#` border line.
154///
155/// Typically used after [`print_hashtag_text`] to close a box that was
156/// opened with [`print_hashtag_title`]. For a complete box in one call,
157/// consider using [`print_hashtag_box`] instead.
158///
159/// # Arguments
160///
161/// * `width` - The total width of the closing line in characters.
162///
163/// # Examples
164///
165/// ```rust
166/// clprint::print_hashtag_line(40);
167/// ```
168///
169/// Output:
170/// ```text
171/// ########################################
172/// ```
173pub fn print_hashtag_line(width: usize) {
174 println!("{}", "#".repeat(width));
175}
176
177/// Prints a complete hashtag box with a title, body text, and closing border.
178///
179/// This is a convenience function that combines [`print_hashtag_title`],
180/// [`print_hashtag_text`], and [`print_hashtag_line`] into a single call.
181///
182/// # Arguments
183///
184/// * `title` - The title text displayed at the top of the box.
185/// * `text` - The body text displayed inside the box. Supports `\n` for manual
186/// line breaks and automatically word-wraps to fit within `width`.
187/// * `width` - The total width of the box in characters, including borders.
188/// Minimum recommended value is `10`.
189///
190/// # Examples
191///
192/// ```rust
193/// clprint::print_hashtag_box("Hello", "Welcome to clp!", 40);
194/// ```
195///
196/// Output:
197/// ```text
198/// ########################################
199/// # Hello #
200/// ########################################
201/// # Welcome to clp! #
202/// ########################################
203/// ```
204///
205/// Multi-line text with wrapping:
206/// ```rust
207/// clprint::print_hashtag_box(
208/// "Warning",
209/// "This is a very long warning message that will be automatically wrapped.",
210/// 30
211/// );
212/// ```
213///
214/// # Panics
215///
216/// Panics if `width` is less than 4. See [`print_hashtag_text`] for details.
217pub fn print_hashtag_box(title: &str, text: &str, width: usize) {
218 print_hashtag_title(title, width);
219 print_hashtag_text(text, width);
220 print_hashtag_line(width);
221}
222
223/// Prints a formatted table centered on the terminal, framed by `#` characters
224/// on the left and right edges with space padding between the `#` and the table.
225///
226/// # Arguments
227///
228/// * `table` - A slice of rows, where each row is a `Vec<String>` of cell values.
229/// All rows are assumed to have the same number of columns.
230/// * `table_width` - The desired character width of the inner table (cell borders
231/// included), not counting the `#` or space padding on either side.
232/// * `width` - The total character width of the full output line, including the
233/// table, space padding, and the `#` characters on both sides.
234///
235/// # Layout
236///
237/// Each output line takes the form:
238///
239/// ```text
240/// #<spaces><table content><spaces>#
241/// ```
242///
243/// Where:
244/// - The single `#` on each side acts as a fixed left/right boundary marker.
245/// - `padding_width` spaces are inserted between each `#` and the table,
246/// centering the table within the total `width`.
247/// - The table itself uses `+---+` style horizontal separators and `| val |`
248/// style cell rows.
249/// - Cell widths are distributed evenly across columns. If `table_width` does
250/// not divide evenly, the `remainder` characters are added as extra dashes/spaces
251/// to the last column.
252///
253/// Example output with space padding:
254///
255/// ```text
256/// # +----------------------+----------------------+-----------------------+ #
257/// # | Alice | 30 | Engineer | #
258/// # +----------------------+----------------------+-----------------------+ #
259/// ```
260///
261/// # Panics
262///
263/// Panics if `table` is empty, as column count is derived from `table[0]`.
264/// May also produce unexpected output or panic if:
265/// - `table_width` is too small to accommodate all columns.
266/// - `width` is less than `table_width + 2`.
267/// - `cell_width` computes to less than 2 (causes underflow in `cell_width - 2`).
268/// - Rows have differing numbers of columns.
269pub fn print_hashtag_table(table: &[Vec<String>], table_width: usize, width: usize) {
270 // Compute cell_width and remaining spaces
271 let column_count = table[0].len();
272 let cell_width = (table_width - column_count - 1) / column_count;
273 let remainder = (table_width - column_count - 1) % column_count;
274 let padding_width = (width - table_width - 2) / 2;
275
276 // Empty String to collect output
277 let mut output = String::new();
278
279 // Assemble Space padding and boundry
280 let mut boundry_left = String::new();
281 boundry_left.push_str(&format!("{:padding_width$}", "#", padding_width = padding_width));
282 let mut boundry_right = String::new();
283 boundry_right.push_str(&format!("{:>padding_width$}", "#", padding_width = padding_width));
284
285 // Assemble the horizontal seperation line
286 let mut sep_line = String::new();
287 let mut column_sep_line = String::new();
288 column_sep_line.push_str(&format!("+{}", "-".repeat(cell_width)).to_string());
289 sep_line.push_str(&boundry_left);
290 sep_line.push_str(&column_sep_line.repeat(column_count));
291 sep_line.push_str(&format!("{}+{}\n", "-".repeat(remainder), boundry_right));
292
293 for line in table{
294 output.push_str(&sep_line.to_string());
295 output.push_str(&boundry_left);
296 for column in line {
297 let mut column_string = String::new();
298 column_string.push_str(&format!("| {:cell_width$} ", column, cell_width = cell_width - 2));
299 output.push_str(&column_string);
300 }
301 output.push_str(&format!("{}|{}\n", " ".repeat(remainder), boundry_right));
302 }
303 output.push_str(&sep_line.to_string());
304 println!("{}", output);
305}