stylish_stringlike/
lib.rs

1//! This is a libary for creating styled spans of text. The style can be
2//! something like an ANSI terminal color/format, or it could be some
3//! markup like enclosing text in html tags.
4//!
5//!
6//! ## Structure
7//! This crate is subdivided into two modules: [`text`] and [`widget`].
8//!
9//! [`text`] provides string-like functionality for styled pieces of text.
10//! Methods such as replacing, slicing, and splitting are supported while
11//! respecting existing style delimiters.
12//!
13//! [`widget`] provides functionality for displaying text objects in useful ways,
14//! such as truncation with a symbol, or repeating a sequence.
15//!
16//! ## Usage
17//!
18//! ```rust
19//! use std::borrow::Cow;
20//! use stylish_stringlike::text::{Joinable, Paintable, Pushable, Replaceable, Sliceable, Span,
21//!     Spans, Tag};
22//! use stylish_stringlike::widget::{Fitable, HBox, TextWidget, TruncationStyle};
23//!
24//! let italic = Tag::new("<i>", "</i>");
25//! let bold = Tag::new("<b>", "</b>");
26//! let underline = Tag::new("<u>", "</u>");
27//!
28//! let foo: Span<Tag> = Span::new(Cow::Borrowed(&italic), Cow::Borrowed("foo"));
29//! let bar: Span<Tag> = Span::new(Cow::Borrowed(&bold), Cow::Borrowed("bar"));
30//!
31//! // Spans of different styles can be joined together.
32//! let foobar = foo.join(&bar);
33//! assert_eq!(format!("{}", foobar), "<i>foo</i><b>bar</b>");
34//!
35//! // Perform literal string replacement with the `replace` method.
36//! let foobaz = foobar.replace("bar", "baz");
37//! assert_eq!(format!("{}", foobaz), "<i>foo</i><b>baz</b>");
38//!
39//! let mut buz: Spans<Tag> = Default::default();
40//! buz.push(&Span::new(Cow::Borrowed(&underline), Cow::Borrowed("buz")));
41//!
42//! // Replace text with styled text objects instead of string literals.
43//! let foobuz = foobar.replace("bar", &buz);
44//! assert_eq!(format!("{}", foobuz), "<i>foo</i><u>buz</u>");
45//!
46//! // Use the `slice` method to slice on bytes.
47//! let foob = foobar.slice(..4).unwrap();
48//! assert_eq!(format!("{}", foob), "<i>foo</i><b>b</b>");
49//!
50//! // Use the `HBox` widget to truncate multiple spans of text to fit in a desired width.
51//! fn make_spans(style: &Tag, text: &str) -> Spans<Tag> {
52//!     let mut spans: Spans<Tag> = Default::default();
53//!     let span: Span<Tag> = Span::new(Cow::Borrowed(style), Cow::Borrowed(text));
54//!     spans = spans.join(&span);
55//!     spans
56//! }
57//! let truncation = TruncationStyle::Inner(Some(Span::new(
58//!     Cow::Borrowed(&underline),
59//!     Cow::Borrowed("…"),
60//! )));
61//! let spans = vec![make_spans(&italic, "abcdefg"), make_spans(&bold, "12345678")];
62//! let hbox = spans
63//!     .iter()
64//!     .map(|s| {
65//!         let b: Box<dyn Fitable<_>> =
66//!             Box::new(TextWidget::<Spans<_>, TruncationStyle<_>>::new(
67//!                 Cow::Borrowed(s),
68//!                 Cow::Borrowed(&truncation),
69//!             ));
70//!         b
71//!     })
72//!     .collect::<HBox<_>>();
73//! assert_eq!(
74//!     format!("{}", hbox.truncate(10)),
75//!     "<i>ab</i><u>…</u><i>fg</i><b>12</b><u>…</u><b>78</b>"
76//! );
77//! ```
78pub mod text;
79pub mod widget;
80
81#[cfg(test)]
82mod test {
83    use super::*;
84    use ansi_term::{ANSIString, ANSIStrings, Color, Style};
85    use std::borrow::Cow;
86    use text::*;
87    use widget::*;
88    fn make_spans(style: &Style, text: &str) -> Spans<Style> {
89        let ansistring: ANSIString = Style::paint(*style, text);
90        let span: Span<Style> = ansistring.into();
91        let mut spans: Spans<Style> = Default::default();
92        spans.push(&span);
93        spans
94    }
95    #[test]
96    fn split_path() {
97        let texts = vec![
98            Color::Black.paint("::"),
99            Color::Red.paint("SomeExtremelyLong"),
100            Color::Blue.paint("::"),
101            Color::Green.paint("RandomAndPoorlyNamed"),
102            Color::Cyan.paint("::"),
103            Color::White.paint("Path"),
104            Color::Yellow.paint("::"),
105        ];
106        let spans: Spans<_> = texts.iter().map(Span::<Style>::from).collect();
107        let ellipsis_string = Color::Blue.paint("…");
108        let ellipsis_span = make_spans(&Color::Blue.normal(), "…");
109        let truncation = TruncationStyle::Inner(ellipsis_span.clone());
110
111        let actual = spans
112            .split("::")
113            .map(|Split { segment, delim }| vec![segment, delim])
114            .flatten()
115            .flatten()
116            .map(|s| {
117                let foo: Box<dyn Fitable<_>> =
118                    Box::new(TextWidget::<Spans<_>, TruncationStyle<_>>::new(
119                        Cow::Owned(s),
120                        Cow::Borrowed(&truncation),
121                    ));
122                foo
123            })
124            .collect::<HBox<_>>()
125            .truncate(20)
126            .to_string();
127        let expected = format!(
128            "{}",
129            ANSIStrings(&[
130                Color::Black.paint("::"),
131                Color::Red.paint("So"),
132                ellipsis_string.clone(),
133                Color::Red.paint("g"),
134                Color::Blue.paint("::"),
135                Color::Green.paint("Ra"),
136                ellipsis_string,
137                Color::Green.paint("d"),
138                Color::Cyan.paint("::"),
139                Color::White.paint("Path"),
140                Color::Yellow.paint("::"),
141            ])
142        );
143        assert_eq!(expected, actual);
144    }
145    #[test]
146    fn split_path_2() {
147        let path = Color::Blue.paint("some//path//with//segments");
148        let span: Span<Style> = path.clone().into();
149        let spans = {
150            let mut spans: Spans<Style> = Default::default();
151            spans.push(&span);
152            spans
153        };
154        let truncation = TruncationStyle::Inner(Some(make_spans(&Color::Blue.normal(), "……")));
155
156        let actual = spans
157            .split("::")
158            .map(|Split { segment, delim }| vec![segment, delim])
159            .flatten()
160            .flatten()
161            .map(|s| {
162                let foo: Box<dyn Fitable<_>> =
163                    Box::new(TextWidget::<Spans<Style>, TruncationStyle<_>>::new(
164                        Cow::Owned(s),
165                        Cow::Borrowed(&truncation),
166                    ));
167                foo
168            })
169            .collect::<HBox<_>>()
170            .truncate(50)
171            .to_string();
172
173        let expected = format!("{}", path);
174        assert_eq!(expected, actual);
175    }
176}