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}