text_grid/cell.rs
1use crate::{Cells, CellsFormatter};
2
3use self::HorizontalAlignment::*;
4use std::{cmp::min, fmt::*};
5
6/// Cell`s style.
7#[derive(Clone, Copy, Default)]
8
9pub struct CellStyle {
10 pub(crate) align_h: Option<HorizontalAlignment>,
11}
12impl CellStyle {
13 pub fn new() -> Self {
14 Self::default()
15 }
16
17 /// Returns the styles in which the empty style has been replaced with the specified style.
18 ///
19 /// Judgment as to whether the style is empty or not is done for each individual element.
20 pub fn or(self, style: CellStyle) -> CellStyle {
21 CellStyle {
22 align_h: self.align_h.or(style.align_h),
23 }
24 }
25
26 pub fn align_h(self, value: HorizontalAlignment) -> Self {
27 CellStyle {
28 align_h: Some(value),
29 }
30 }
31}
32
33/// Horizontal alignments for cell's content.
34#[derive(Clone, Copy)]
35pub enum HorizontalAlignment {
36 Left,
37 Center,
38 Right,
39}
40
41/// A data structure that can be formatted into a cell.
42///
43/// Normally, [`cell()`] or [`cell!`](crate::cell!) is used to create a value that implements `RawCell`.
44///
45/// If you implement `RawCell` for a type, you should also implement [`Cells`] for convenience.
46///
47/// ```
48/// use text_grid::*;
49/// struct X(String);
50///
51/// impl RawCell for X {
52/// fn fmt(&self, s: &mut String) {
53/// s.push_str(&self.0);
54/// }
55/// fn style(&self) -> CellStyle {
56/// CellStyle::new().align_h(HorizontalAlignment::Right)
57/// }
58/// }
59/// impl Cells for X {
60/// fn fmt(f: &mut CellsFormatter<Self>) {
61/// f.content(Cell::new);
62/// }
63/// }
64/// ```
65pub trait RawCell {
66 /// Output the cell text to given buffer.
67 fn fmt(&self, s: &mut String);
68
69 /// Return cell's style.
70 fn style(&self) -> CellStyle {
71 CellStyle::default()
72 }
73
74 fn style_for_body(&self) -> CellStyle {
75 CellStyle::default()
76 }
77}
78impl RawCell for () {
79 fn fmt(&self, _: &mut String) {}
80}
81impl<T: ?Sized + RawCell> RawCell for &T {
82 fn fmt(&self, s: &mut String) {
83 T::fmt(*self, s)
84 }
85 fn style(&self) -> CellStyle {
86 T::style(*self)
87 }
88 fn style_for_body(&self) -> CellStyle {
89 T::style_for_body(*self)
90 }
91}
92impl<T: ?Sized + RawCell> RawCell for &mut T {
93 fn fmt(&self, s: &mut String) {
94 T::fmt(*self, s)
95 }
96 fn style(&self) -> CellStyle {
97 T::style(*self)
98 }
99 fn style_for_body(&self) -> CellStyle {
100 T::style_for_body(*self)
101 }
102}
103impl<T: RawCell> RawCell for Option<T> {
104 fn fmt(&self, s: &mut String) {
105 if let Some(value) = self {
106 value.fmt(s);
107 }
108 }
109 fn style(&self) -> CellStyle {
110 if let Some(value) = self {
111 value.style()
112 } else {
113 CellStyle::default()
114 }
115 }
116 fn style_for_body(&self) -> CellStyle {
117 if let Some(value) = self {
118 value.style_for_body()
119 } else {
120 CellStyle::default()
121 }
122 }
123}
124impl<T: RawCell, E: RawCell> RawCell for std::result::Result<T, E> {
125 fn fmt(&self, s: &mut String) {
126 match self {
127 Ok(value) => value.fmt(s),
128 Err(value) => value.fmt(s),
129 }
130 }
131 fn style(&self) -> CellStyle {
132 match self {
133 Ok(value) => value.style(),
134 Err(value) => value.style(),
135 }
136 }
137 fn style_for_body(&self) -> CellStyle {
138 match self {
139 Ok(value) => value.style_for_body(),
140 Err(value) => value.style_for_body(),
141 }
142 }
143}
144
145struct DisplayCellSource<T: Display>(T);
146impl<T: Display> RawCell for DisplayCellSource<T> {
147 fn fmt(&self, s: &mut String) {
148 write!(s, "{}", self.0).unwrap()
149 }
150}
151
152/// Create [`Cell`] from [`Display`].
153///
154/// The returned value owns the value passed in.
155/// Therefore, the returned value can not be move out of the lifetime of the passed value.
156///
157/// ```ignore
158/// use text_grid::*;
159///
160/// fn f_error() -> Cell<impl RawCell> {
161/// let s = String::from("ABC");
162/// cell(&s) // Error : returns a value referencing data owned by the current function
163/// }
164/// ```
165///
166/// ```
167/// use text_grid::*;
168///
169/// fn f_ok() -> Cell<impl RawCell> {
170/// let s = String::from("ABC");
171/// cell(s) // OK
172/// }
173/// ```
174pub fn cell(value: impl Display) -> Cell<impl RawCell> {
175 Cell::new(DisplayCellSource(value))
176}
177
178struct FmtFnCellSource<F>(F);
179impl<F: Fn(&mut String) -> Result> RawCell for FmtFnCellSource<F> {
180 fn fmt(&self, s: &mut String) {
181 (self.0)(s).unwrap()
182 }
183}
184
185/// Create [`Cell`] from closure that call [`std::write!`] macro.
186///
187/// # Examples
188///
189/// ```
190/// use text_grid::*;
191/// use std::fmt::Write;
192///
193/// let s = String::from("ABC");
194/// let cell_a = cell_by(|f| write!(f, "{}", &s));
195/// let cell_b = cell_by(|f| write!(f, "{}", &s));
196/// ```
197pub fn cell_by<F: Fn(&mut String) -> Result>(f: F) -> Cell<impl RawCell> {
198 Cell::new(FmtFnCellSource(f))
199}
200
201/// Create [`Cell`] via runtime expression interpolation, as in [`format!`].
202///
203/// Use the `format!` syntax to create [`Cell`]. See [`std::fmt`] for more information.
204///
205/// # Examples
206/// ```
207/// use text_grid::*;
208/// struct RowData {
209/// a: f64,
210/// b: f64,
211/// }
212/// impl Cells for RowData {
213/// fn fmt(f: &mut CellsFormatter<Self>) {
214/// f.column("a", |s| cell!("{:.2}", s.a).right());
215/// f.column("b", |s| cell!("{:.3}", s.b).right());
216/// }
217/// }
218///
219/// let rows = [
220/// RowData { a: 1.10, b: 1.11 },
221/// RowData { a: 1.00, b: 0.10 },
222/// ];
223/// let g = to_grid(rows);
224///
225/// assert_eq!(format!("\n{g}"), r#"
226/// a | b |
227/// ------|-------|
228/// 1.10 | 1.110 |
229/// 1.00 | 0.100 |
230/// "#);
231/// ```
232///
233/// # Arguments ownership
234///
235/// This macro consumes the variable used in the argument.
236/// ```ignore
237/// use text_grid::*;
238///
239/// let s = String::from("ABC");
240/// let cell_a = cell!("{}", &s); // `s` moved into `cell_a` here
241/// let cell_b = cell!("{}", &s); // ERROR : `s` used here after move
242/// ```
243///
244/// To avoid consume variables, use only variables that implements `Copy` .
245///
246/// ```
247/// use text_grid::*;
248///
249/// let s = String::from("ABC");
250/// let s = &s; // immutable reference implements `Copy`.
251/// let cell_a = cell!("{}", s);
252/// let cell_b = cell!("{}", s); // OK
253/// // Return value owns the reference.
254/// // Therefore, the returned value can not be move out of the lifetime of the reference.
255/// ```
256///
257/// or use [`cell_by`] and `write!`.
258///
259/// ```
260/// use text_grid::*;
261/// use std::fmt::Write;
262///
263/// let s = String::from("ABC");
264/// let cell_a = cell_by(|f| write!(f, "{}", &s));
265/// let cell_b = cell_by(|f| write!(f, "{}", &s));
266/// // Return value owns the reference.
267/// // Therefore, the returned value can not be move out of the lifetime of the reference.
268/// ```
269///
270/// or use [`cell()`] and `format!`.
271///
272/// ```
273/// use text_grid::*;
274///
275/// let s = String::from("ABC");
276/// let cell_a = cell(format!("{}", &s));
277/// let cell_b = cell(format!("{}", &s));
278/// // Return value owns formatted string.
279/// // Therefore, the returned value can move anywhere.
280/// ```
281#[macro_export]
282macro_rules! cell {
283 ($ ( $ arg : tt ) *) => { {
284 use std::fmt::Write;
285 $crate::cell_by(move |f| write!(f, $($arg)*))
286 }
287 };
288}
289
290/// Implementation of [`RawCell`] that can specify styles.
291pub struct Cell<T> {
292 source: T,
293 style: CellStyle,
294}
295impl<T: RawCell> RawCell for Cell<T> {
296 fn fmt(&self, s: &mut String) {
297 self.source.fmt(s)
298 }
299 fn style(&self) -> CellStyle {
300 self.style
301 }
302}
303impl<T: RawCell> Cells for Cell<T> {
304 fn fmt(f: &mut CellsFormatter<Self>) {
305 f.content_cell(|s| s);
306 }
307}
308
309impl<T: RawCell> Cell<T> {
310 /// Create a new `Cell` with specified [`RawCell`].
311 pub fn new(source: T) -> Self {
312 let style = source.style();
313 Cell { source, style }
314 }
315
316 /// Return the cell with horizontal alignment set to the left.
317 pub fn left(self) -> Self {
318 self.with_align_h(Left)
319 }
320
321 /// Return the cell with horizontal alignment set to the right.
322 pub fn right(self) -> Self {
323 self.with_align_h(Right)
324 }
325
326 /// Return the cell with horizontal alignment set to the center.
327 pub fn center(self) -> Self {
328 self.with_align_h(Center)
329 }
330
331 /// Return the cell with aligned baseline.
332 ///
333 /// ```rust
334 /// use text_grid::*;
335 ///
336 /// struct Source(&'static str);
337 ///
338 /// impl Cells for Source {
339 /// fn fmt(f: &mut CellsFormatter<Self>) {
340 /// f.column("default", |x| &x.0);
341 /// f.column("baseline", |x| cell(&x.0).baseline("-"));
342 /// }
343 /// }
344 /// let rows = [
345 /// Source("1-2345"),
346 /// Source("1234-5"),
347 /// Source("12345"),
348 /// ];
349 /// let g = to_grid(rows);
350 ///
351 /// assert_eq!(format!("\n{g}"), r#"
352 /// default | baseline |
353 /// ---------|------------|
354 /// 1-2345 | 1-2345 |
355 /// 1234-5 | 1234-5 |
356 /// 12345 | 12345 |
357 /// "#);
358 /// ```
359 pub fn baseline(self, baseline: &str) -> impl Cells {
360 let mut value = String::new();
361 self.fmt(&mut value);
362 BaselineAlignedCell::new(value, baseline)
363 }
364
365 /// Return the cell with an empty style replaced by the specified style.
366 ///
367 /// Judgment as to whether the style is empty or not is done for each individual element.
368 pub fn with_base_style(self, style: CellStyle) -> Self {
369 Cell {
370 source: self.source,
371 style: self.style.or(style),
372 }
373 }
374 fn with_align_h(self, align_h: HorizontalAlignment) -> Self {
375 Cell {
376 source: self.source,
377 style: CellStyle {
378 align_h: Some(align_h),
379 },
380 }
381 }
382}
383impl Cell<String> {
384 /// Create a new `Cell` with empty string and empty style.
385 pub fn empty() -> Self {
386 Self::new(String::new())
387 }
388}
389
390macro_rules! impl_cell_source {
391 ($t:ty, $align:expr ) => {
392 impl RawCell for $t {
393 fn fmt(&self, s: &mut String) {
394 write!(s, "{}", self).unwrap()
395 }
396 fn style_for_body(&self) -> CellStyle {
397 CellStyle {
398 align_h: Some($align),
399 }
400 }
401 }
402 impl Cells for $t {
403 fn fmt(f: &mut CellsFormatter<Self>) {
404 f.content_cell(|x| x);
405 }
406 }
407 };
408}
409
410impl_cell_source!(u8, Right);
411impl_cell_source!(i8, Right);
412impl_cell_source!(u16, Right);
413impl_cell_source!(i16, Right);
414impl_cell_source!(u32, Right);
415impl_cell_source!(i32, Right);
416impl_cell_source!(u64, Right);
417impl_cell_source!(i64, Right);
418impl_cell_source!(u128, Right);
419impl_cell_source!(i128, Right);
420impl_cell_source!(isize, Right);
421impl_cell_source!(usize, Right);
422impl_cell_source!(String, Left);
423impl_cell_source!(str, Left);
424impl_cell_source!(char, Center);
425impl_cell_source!(bool, Center);
426
427/// A cell with aligned baseline.
428///
429/// Use [`Cell::baseline`] to create an instance of this type.
430struct BaselineAlignedCell {
431 value: String,
432 baseline_offset: usize,
433}
434impl BaselineAlignedCell {
435 fn new(value: String, baseline: &str) -> Self {
436 let baseline_offset = value.find(baseline).unwrap_or(value.len());
437 Self {
438 value,
439 baseline_offset,
440 }
441 }
442 fn left(&self) -> &str {
443 &self.value[..self.baseline_offset]
444 }
445 fn right(&self) -> &str {
446 &self.value[self.baseline_offset..]
447 }
448}
449
450impl Cells for BaselineAlignedCell {
451 fn fmt(f: &mut CellsFormatter<Self>) {
452 f.content(|this| cell(this.left()).right());
453 f.content(|this| cell(this.right()).left());
454 }
455}
456
457impl Cells for f32 {
458 fn fmt(f: &mut CellsFormatter<Self>) {
459 f.content(|this| cell(this).baseline("."))
460 }
461}
462
463impl Cells for f64 {
464 fn fmt(f: &mut CellsFormatter<Self>) {
465 f.content(|this| cell(this).baseline("."))
466 }
467}
468
469/// Create [`Cells`] for float numbers via runtime expression interpolation.
470///
471/// # Examples
472///
473/// ```
474/// use text_grid::*;
475/// let s = cells_schema::<f64>(|f| {
476/// f.column("", |x| cell!("{x:e}"));
477/// f.column("e", |x| cells_f!("{x:e}"));
478/// f.column(".2e", |x| cells_f!("{x:.2e}"));
479/// f.column("E", |x| cells_f!("{x:E}"));
480/// f.column("debug", |x| cells_f!("{x:?}"));
481/// });
482///
483/// let g = to_grid_with_schema(vec![1.0, 0.95, 123.45, 0.000001, 1.0e-20, 10000000000.0], s);
484/// assert_eq!(format!("\n{g}"), OUTPUT);
485///
486/// const OUTPUT: &str = r"
487/// | e | .2e | E | debug |
488/// ----------|--------------|------------|--------------|----------------------|
489/// 1e0 | 1 e 0 | 1.00 e 0 | 1 E 0 | 1.0 |
490/// 9.5e-1 | 9.5 e -1 | 9.50 e -1 | 9.5 E -1 | 0.95 |
491/// 1.2345e2 | 1.2345 e 2 | 1.23 e 2 | 1.2345 E 2 | 123.45 |
492/// 1e-6 | 1 e -6 | 1.00 e -6 | 1 E -6 | 1 e -6 |
493/// 1e-20 | 1 e -20 | 1.00 e -20 | 1 E -20 | 1 e -20 |
494/// 1e10 | 1 e 10 | 1.00 e 10 | 1 E 10 | 10000000000.0 |
495/// ";
496/// ```
497#[macro_export]
498macro_rules! cells_f {
499 ($($t:tt)*) => {
500 $crate::cells_f(format!($($t)*))
501 };
502}
503
504/// Create [`Cells`] for float numbers from [`Display`].
505///
506/// Format in the same way as [`cells_f!`] macro.
507pub fn cells_f(value: impl Display) -> impl Cells {
508 ExpCells::new(value.to_string())
509}
510struct ExpCells {
511 value: String,
512 offset_dot: usize,
513 offset_e: usize,
514}
515impl ExpCells {
516 fn new(value: String) -> Self {
517 let offset_e = value.rfind(['e', 'E']).unwrap_or(value.len());
518 let offset_dot = min(value.find('.').unwrap_or(value.len()), offset_e);
519 Self {
520 value,
521 offset_dot,
522 offset_e,
523 }
524 }
525}
526
527impl Cells for ExpCells {
528 fn fmt(f: &mut CellsFormatter<Self>) {
529 f.stretch()
530 .content(|x| cell(&x.value[..x.offset_dot]).right());
531 f.content(|x| {
532 if x.offset_dot < x.offset_e {
533 &x.value[x.offset_dot..x.offset_e]
534 } else {
535 ""
536 }
537 });
538 f.content(|x| {
539 Cell::new(if x.offset_e < x.value.len() {
540 Ok(cell!(" {} ", &x.value[x.offset_e..x.offset_e + 1]))
541 } else {
542 Err(())
543 })
544 });
545 f.content(|x| cell(&x.value[min(x.offset_e + 1, x.value.len())..]).right());
546 }
547}