term_painter/lib.rs
1//! This is a crate for coloring and formatting terminal output. Simple
2//! example:
3//!
4//! ```
5//! extern crate term_painter;
6//!
7//! use term_painter::ToStyle;
8//! use term_painter::Color::*;
9//! use term_painter::Attr::*;
10//!
11//! fn main() {
12//! println!("{} or {} or {}",
13//! Red.paint("Red"),
14//! Bold.paint("Bold"),
15//! Red.bold().paint("Both!")
16//! );
17//! }
18//! ```
19//!
20//! This crate uses [`rust-lang/term`][term] to do the formatting. Of course,
21//! you can use `term` directly, but it's kinda clumsy. Hence this library.
22//!
23//! [term]: https://crates.io/crates/term
24//!
25//!
26//! How to use it
27//! -------------
28//! Formatting is done in two steps:
29//!
30//! 1. Creating a style
31//! 2. Use this style to "paint" something and get a `Painted` object
32//!
33//!
34//! 1. Creating a style
35//! -------------------
36//! To create a style a startpoint is needed: This can either be a startpoint
37//! with an attached modifier (like `Red`: modifies the fg-color) or the
38//! `Plain` startpoint, which does not modify anything.
39//! After that, the startpoint can be modified with modifiers like `bold()` or
40//! `fg()`.
41//!
42//! ```
43//! extern crate term_painter;
44//!
45//! use term_painter::ToStyle;
46//! use term_painter::Color::*;
47//! use term_painter::Attr::*;
48//!
49//! fn main() {
50//! let x = 5;
51//!
52//! // These two are equivalent: nothing is formatted/painted
53//! println!("{} | {}", x, Plain.paint(x));
54//!
55//! // These two are equivalent, too
56//! println!("{} | {}", Red.paint(x), Plain.fg(Red).paint(x));
57//! }
58//! ```
59//! You can chain as many modifiers as you want. Every modifier overrides
60//! preceding modifier:
61//!
62//! ```
63//! # use term_painter::Attr::*;
64//! # use term_painter::Color::*;
65//! # use term_painter::ToStyle;
66//!
67//! // blue, not red
68//! println!("{}", Plain.fg(Red).fg(Blue).paint("Apple"));
69//! ```
70//!
71//! 2. Use the style
72//! ----------------
73//! After building the style, you can use it in two different ways.
74//!
75//! One way is to call `paint` to use it on some object.
76//! `paint` will return the wrapper object `Painted` that holds your object and
77//! the specified style. `Painted` implements any formatting trait (like
78//! `Display` and `Debug`) if and only if the type of the given Object, `T`,
79//! does. So a `Painted` object can be printed via `println!` or similar macros.
80//! When it gets printed, it will apply the given style before printing the
81//! object of type `T` and will reset the style after printing.
82//!
83//! `Note`: `paint` will consume the passed object. This is no problem when
84//! passing constant literals (like `paint("cheesecake")`) or types that are
85//! `Copy`. Otherwise it could be confusing because just printing should not
86//! consume a variable. To prevent consuming, just pass a reference to the
87//! object (with `&`). Example:
88//!
89//! ```
90//! extern crate term_painter;
91//!
92//! use term_painter::ToStyle;
93//! use term_painter::Color::*;
94//! use term_painter::Attr::*;
95//!
96//! fn main() {
97//! let non_copy = "cake".to_string(); // String is *not* Copy
98//! let copy = 27; // i32 *is* Copy
99//!
100//! println!("{}", Plain.paint(&non_copy));
101//! println!("{}", Plain.paint(©));
102//! // non_copy is still usable here...
103//! // copy is still usable here...
104//!
105//! println!("{}", Plain.paint(non_copy));
106//! println!("{}", Plain.paint(copy));
107//! // non_copy was moved into `paint`, so it not usable anymore...
108//! // copy is still usable here...
109//! }
110//! ```
111//!
112//! Another way is to call `with`. `with` takes another function (usually a
113//! closure) and everything that is printed within that closure is formatted
114//! with the given style. Specifically, `with()` sets the given style,
115//! calls the given function and resets the style afterwards. It can be
116//! chained and used together with `paint()`. Inner calls will overwrite
117//! outer calls of `with`.
118//!
119//! ```
120//! extern crate term_painter;
121//!
122//! use term_painter::ToStyle;
123//! use term_painter::Color::*;
124//! use term_painter::Attr::*;
125//!
126//! fn main() {
127//! Red.with(|| {
128//! print!("JustRed");
129//! Bold.with(|| {
130//! print!(" BoldRed {} BoldRed ", Underline.paint("Underline"));
131//! });
132//! print!("JustRed ");
133//!
134//! print!("{}", Blue.paint("Blue (overwrite) "));
135//! Green.with(|| {
136//! println!("Green (overwrite)");
137//! });
138//! });
139//! }
140//! ```
141//!
142//! Some Notes
143//! ----------
144//! If you don't want to pollute your namespace with `Color` and `Attr` names,
145//! you can use a more qualified name (`Color::Red.paint(..)`) and remove these
146//! `use` statements:
147//!
148//! - `use term_painter::Color::*;`
149//! - `use term_painter::Attr::*;`
150//!
151//! Please note that global state is changed when printing a `Painted`
152//! object. This means that some state is set before and reset after printing.
153//! This means that, for example, using this library in `format!` or `write!`
154//! won't work. The color formatting is not stored in the resulting string.
155//! Although Unix terminals do modify color and formatting by printing special
156//! control characters, Windows and others do not. This library uses the
157//! plattform independent library `term`, thus saving formatted text in a
158//! string not possible. This was a design choice.
159//!
160//! This crate also assumes that the terminal state is not altered by anything
161//! else. Calling `term` function directly might result in strange behaviour.
162//! This is due to the fact that one can not read the current terminal state.
163//! In order to work like this, this crate needs to track terminal state
164//! itself. However, there shouldn't be any problems when the terminal state
165//! is completely reset in between using those two different methods.
166//!
167//! Another possible source of confusion might be multithreading. Terminal
168//! state and handles are hold in thread local variables. If two terminal
169//! handles would reference the same physical terminal, those two threads could
170//! interfere with each other. I have not tested it, though. Usually, you don't
171//! want to print to the same Terminal in two threads simultanously anyway.
172//!
173//! Functions of `term` sometimes return a `Result` that is `Err` when the
174//! function fails to set the state. However, this crate silently ignores those
175//! failures. To check the capabilities of the terminal, use `term` directly.
176//!
177
178extern crate term;
179
180use std::default::Default;
181use std::fmt;
182use std::cell::RefCell;
183
184
185/// Everything that can be seen as part of a style. This is the core of this
186/// crate. All functions ("style modifier") consume self and return a modified
187/// version of the style.
188pub trait ToStyle : Sized {
189 fn to_style(self) -> Style;
190
191 /// Convenience method for modifying the style before it's returned.
192 fn to_mapped_style<F>(self, func: F) -> Style
193 where F: FnOnce(&mut Style)
194 {
195 let mut s = self.to_style();
196 func(&mut s);
197 s
198 }
199
200 /// Sets the foreground (text) color.
201 fn fg(self, c: Color) -> Style {
202 self.to_mapped_style(|s| s.fg = c)
203 }
204
205 /// Sets the background color.
206 fn bg(self, c: Color) -> Style {
207 self.to_mapped_style(|s| s.bg = c)
208 }
209
210 /// Makes the text bold.
211 fn bold(self) -> Style {
212 self.to_mapped_style(|s| s.set_bold(Some(true)))
213 }
214
215 /// Dim mode.
216 fn dim(self) -> Style {
217 self.to_mapped_style(|s| s.set_dim(Some(true)))
218 }
219
220 /// Underlines the text.
221 fn underline(self) -> Style {
222 self.to_mapped_style(|s| s.set_underline(Some(true)))
223 }
224
225 /// Removes underline-attribute.
226 fn not_underline(self) -> Style {
227 self.to_mapped_style(|s| s.set_underline(Some(false)))
228 }
229
230 /// Underlines the text.
231 fn blink(self) -> Style {
232 self.to_mapped_style(|s| s.set_blink(Some(true)))
233 }
234
235 /// Underlines the text.
236 fn reverse(self) -> Style {
237 self.to_mapped_style(|s| s.set_reverse(Some(true)))
238 }
239
240 /// Secure mode.
241 fn secure(self) -> Style {
242 self.to_mapped_style(|s| s.set_secure(Some(true)))
243 }
244
245 /// Wraps the style specified in `self` and something of arbitrary type
246 /// into a `Painted`. When `Painted` is printed it will print the arbitrary
247 /// something with the given style.
248 fn paint<T>(&self, obj: T) -> Painted<T>
249 where Self: Clone
250 {
251 Painted {
252 style: self.clone().to_style(),
253 obj: obj,
254 }
255 }
256
257 /// Executes the given function, applying the style information before
258 /// calling it and resetting after it finished.
259 fn with<F, R>(&self, f: F) -> R
260 where F: FnOnce() -> R,
261 Self: Clone
262 {
263 // Shorthand for the new style and the style that was active before
264 let new = self.clone().to_style();
265 let before = CURR_STYLE.with(|curr| curr.borrow().clone());
266
267 // Apply the new style and setting the merged style as CURR_STYLE
268 let _ = new.apply();
269 CURR_STYLE.with(|curr| *curr.borrow_mut() = before.and(new));
270
271 let out = f();
272
273 // Revert to the style that was active before and set it as current
274 let _ = before.revert_to();
275 CURR_STYLE.with(|curr| *curr.borrow_mut() = before);
276
277 out
278 }
279}
280
281/// Lists all possible Colors. It implements `ToStyle` so it's possible to call
282/// `ToStyle`'s methods directly on a `Color` variant like:
283///
284/// ```
285/// # use term_painter::{Attr, Color, ToStyle};
286///
287/// println!("{}", Color::Red.bold().paint("Red and bold"));
288/// ```
289///
290/// It is not guaranteed that the local terminal supports all of those colors.
291/// As already mentioned in the module documentation, you should use `term`
292/// directly to check the terminal's capabilities.
293///
294/// **Note**: Using `Color::NotSet` will *not* reset the color to the default
295/// terminal color.
296#[derive(Debug, Copy, Clone, PartialEq, Eq)]
297pub enum Color {
298 NotSet,
299 Black,
300 Red,
301 Green,
302 Yellow,
303 Blue,
304 Magenta,
305 Cyan,
306 White,
307 BrightBlack,
308 BrightRed,
309 BrightGreen,
310 BrightYellow,
311 BrightBlue,
312 BrightMagenta,
313 BrightCyan,
314 BrightWhite,
315 Custom(u32),
316}
317
318impl Color {
319 /// Returns the associated constant from `term::color::Color`.
320 fn term_constant(&self) -> Option<term::color::Color> {
321 match *self {
322 Color::NotSet => None,
323 Color::Black => Some(term::color::BLACK),
324 Color::Red => Some(term::color::RED),
325 Color::Green => Some(term::color::GREEN),
326 Color::Yellow => Some(term::color::YELLOW),
327 Color::Blue => Some(term::color::BLUE),
328 Color::Magenta => Some(term::color::MAGENTA),
329 Color::Cyan => Some(term::color::CYAN),
330 Color::White => Some(term::color::WHITE),
331 Color::BrightBlack => Some(term::color::BRIGHT_BLACK),
332 Color::BrightRed => Some(term::color::BRIGHT_RED),
333 Color::BrightGreen => Some(term::color::BRIGHT_GREEN),
334 Color::BrightYellow => Some(term::color::BRIGHT_YELLOW),
335 Color::BrightBlue => Some(term::color::BRIGHT_BLUE),
336 Color::BrightMagenta => Some(term::color::BRIGHT_MAGENTA),
337 Color::BrightCyan => Some(term::color::BRIGHT_CYAN),
338 Color::BrightWhite => Some(term::color::BRIGHT_WHITE),
339 Color::Custom(c) => Some(c)
340 }
341 }
342}
343
344impl Default for Color {
345 fn default() -> Self {
346 Color::NotSet
347 }
348}
349
350impl ToStyle for Color {
351 /// Returns a Style with default values and the `self` color as foreground
352 /// color.
353 fn to_style(self) -> Style {
354 Style {
355 fg: self,
356 .. Style::default()
357 }
358 }
359}
360
361/// Lists possible attributes. It implements `ToStyle` so it's possible to call
362/// `ToStyle`'s methods directly on a `Attr` variant like:
363///
364/// ```
365/// # use term_painter::{Attr, Color, ToStyle};
366///
367/// println!("{}", Attr::Bold.fg(Color::Red).paint("Red and bold"));
368/// ```
369///
370/// It is not guaranteed that the local terminal supports all of those
371/// formatting options. As already mentioned in the module documentation, you
372/// should use `term` directly to check the terminal's capabilities.
373///
374/// For more information about enum variants, see `term::Attr` Documentation.
375#[derive(Debug, Copy, Clone, PartialEq, Eq)]
376pub enum Attr {
377 /// Just default style
378 Plain,
379 Bold,
380 Dim,
381 Underline,
382 Blink,
383 Reverse,
384 Secure,
385}
386
387impl ToStyle for Attr {
388 /// Returns a Style with default values and the `self` attribute enabled.
389 fn to_style(self) -> Style {
390 let mut s = Style::default();
391 match self {
392 Attr::Plain => {},
393 Attr::Bold => s.set_bold(Some(true)),
394 Attr::Dim => s.set_dim(Some(true)),
395 Attr::Underline => s.set_underline(Some(true)),
396 Attr::Blink => s.set_blink(Some(true)),
397 Attr::Reverse => s.set_reverse(Some(true)),
398 Attr::Secure => s.set_secure(Some(true)),
399 }
400 s
401 }
402}
403
404/// Saves all properties of a style. Implements `ToStyle`, so you can call
405/// style modifiers on it.
406#[derive(Debug, Copy, Clone, PartialEq, Eq)]
407pub struct Style {
408 pub fg: Color,
409 pub bg: Color,
410 // Each attribute was `Option<bool>` once. To reduce struct size, the
411 // Option type is simulated with 2 bits for each attribute. The first
412 // attribute in the name uses the MSBs, the last attribute the LSBs.
413 // 00 => None, 10 => Some(false), 11 => Some(true)
414 bold_dim_underline_blink: u8,
415 reverse_secure: u8,
416}
417
418
419impl Default for Style {
420 fn default() -> Self {
421 Style {
422 fg: Color::default(),
423 bg: Color::default(),
424 bold_dim_underline_blink: 0,
425 reverse_secure: 0,
426 }
427 }
428}
429
430thread_local!(
431 static TERM: RefCell<Option<Box<term::StdoutTerminal>>> = RefCell::new(term::stdout())
432);
433thread_local!(
434 static CURR_STYLE: RefCell<Style> = RefCell::new(Style::default())
435);
436
437// Macro to generate getter and setter for all attributes. This hides almost
438// all bit magic in here.
439macro_rules! gen_getter {
440 ($getter:ident, $setter:ident, $var:ident, $pos:expr) => {
441 pub fn $getter(&self) -> Option<bool> {
442 // shift important bits to the right and mask them
443 match (self.$var >> ($pos * 2)) & 0b11 {
444 0b10 => Some(false),
445 0b11 => Some(true),
446 _ => None,
447 }
448 }
449
450 pub fn $setter(&mut self, v: Option<bool>) {
451 match v {
452 None => {
453 // Set important bits to 00
454 self.$var &= !(0b11 << ($pos*2));
455 },
456 Some(false) => {
457 // Set important bits to 10
458 self.$var &= !(0b01 << ($pos*2));
459 self.$var |= 0b10 << ($pos*2);
460 },
461 Some(true) => {
462 // Set important bits to 11
463 self.$var |= 0b11 << ($pos*2);
464 },
465 }
466 }
467 }
468}
469
470impl Style {
471 // Generate a bunch of getters and setters to hide bit fiddling.
472 gen_getter!(get_bold, set_bold, bold_dim_underline_blink, 3);
473 gen_getter!(get_dim, set_dim, bold_dim_underline_blink, 2);
474 gen_getter!(get_underline, set_underline, bold_dim_underline_blink, 1);
475 gen_getter!(get_blink, set_blink, bold_dim_underline_blink, 0);
476 gen_getter!(get_reverse, set_reverse, reverse_secure, 3);
477 gen_getter!(get_secure, set_secure, reverse_secure, 2);
478
479
480 fn apply(&self) -> Result<(), fmt::Error> {
481 // Like `try!`, but converts `term`-Error into `fmt::Error`
482 macro_rules! try_term {
483 ($e:expr) => { $e.map_err(|_| fmt::Error)? }
484 }
485
486 TERM.with(|term_opt| {
487 let mut tmut = term_opt.borrow_mut();
488 let t = match tmut.as_mut() {
489 None => return Err(fmt::Error),
490 Some(t) => t,
491 };
492
493 // Apply colors if set.
494 if let Some(c) = self.fg.term_constant() {
495 try_term!(t.fg(c));
496 }
497 if let Some(c) = self.bg.term_constant() {
498 try_term!(t.bg(c));
499 }
500
501 // For all attributes: Apply, when set.
502 if let Some(true) = self.get_bold() {
503 try_term!(t.attr(term::Attr::Bold));
504 }
505 if let Some(true) = self.get_dim() {
506 try_term!(t.attr(term::Attr::Dim));
507 }
508 if let Some(u) = self.get_underline() {
509 try_term!(t.attr(term::Attr::Underline(u)));
510 }
511 if let Some(true) = self.get_blink() {
512 try_term!(t.attr(term::Attr::Blink));
513 }
514 if let Some(true) = self.get_reverse() {
515 try_term!(t.attr(term::Attr::Reverse))
516 }
517 if let Some(true) = self.get_secure() {
518 try_term!(t.attr(term::Attr::Secure))
519 }
520
521 Ok(())
522 })
523 }
524
525 /// `o` overrides values of `self`.
526 fn and(&self, o: Style) -> Style {
527 // Some shortcuts for bitfields.
528 let ax = self.bold_dim_underline_blink;
529 let ay = o.bold_dim_underline_blink;
530 let bx = self.reverse_secure;
531 let by = o.reverse_secure;
532
533 // The following is equivalent to
534 // `s.set_attr(o.get_attr().and(self.get_attr()));`
535 // for every attribute.
536 //
537 // But we can do better with some bit operations.
538 // There are two bits for each attribute: The setbit and valuebit.
539 // The resulting setbit is just an bitwise OR of both input setbits.
540 // The resulting valuebit is either the one of y (if y's set bit is
541 // set) or the one of x (otherwise).
542 let az = ((ax | ay) & 0b10101010) | (((ay >> 1) & ay | !(ay >> 1) & ax) & 0b01010101);
543 let bz = ((bx | by) & 0b10101010) | (((by >> 1) & by | !(by >> 1) & bx) & 0b01010101);
544
545 Style {
546 fg: if o.fg == Color::NotSet { self.fg } else { o.fg },
547 bg: if o.bg == Color::NotSet { self.bg } else { o.bg },
548 bold_dim_underline_blink: az,
549 reverse_secure: bz,
550 }
551 }
552
553 /// Resets the whole terminal and applies this style.
554 fn revert_to(&self) -> Result<(), fmt::Error> {
555 TERM.with(|term_opt| {
556 let mut tmut = term_opt.borrow_mut();
557 tmut.as_mut()
558 .and_then(|t| t.reset().ok())
559 .ok_or(fmt::Error)
560 })?;
561 self.apply()
562 }
563}
564
565impl ToStyle for Style {
566 /// Dummy implementation that just returns `self`.
567 fn to_style(self) -> Style {
568 self
569 }
570}
571
572/// Wraps an object of type `T` and a style. When attempting to print it, the
573/// given style is applied before printing and reset afterwards.
574/// All formatting traits (`Display`, `Debug`, ...) that are implemented
575/// for `T` are also implemented the wrapper type `Painted<T>`.
576pub struct Painted<T> {
577 style: Style,
578 obj: T,
579}
580
581macro_rules! impl_format {
582 ($symbol:expr, $fmt:ident) => {
583 impl<T: fmt::$fmt> fmt::$fmt for Painted<T> {
584 fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
585 self.style.with(|| fmt::$fmt::fmt(&self.obj, f))
586 }
587 }
588 }
589}
590
591impl_format!("{}", Display);
592impl_format!("{:?}", Debug);
593impl_format!("{:o}", Octal);
594impl_format!("{:x}", LowerHex);
595impl_format!("{:X}", UpperHex);
596impl_format!("{:p}", Pointer);
597impl_format!("{:b}", Binary);
598impl_format!("{:e}", LowerExp);
599impl_format!("{:E}", UpperExp);
600
601
602// ----- Tests ------
603#[cfg(test)]
604mod test {
605 use super::Color::*;
606 use super::Attr::*;
607 use super::{ToStyle, Style};
608
609 #[test]
610 fn modifier_order() {
611 // The order of modifiers shouldn't play a role.
612 assert_eq!(Plain.bold().fg(Red), Plain.fg(Red).bold());
613 assert_eq!(Plain.bold().bg(Red), Plain.bg(Red).bold());
614 assert_eq!(Plain.underline().fg(Red), Plain.fg(Red).underline());
615
616 // The startpoints should have the same effect as the modifier.
617 assert_eq!(Red.to_style(), Plain.fg(Red));
618 assert_eq!(Bold.to_style(), Plain.bold());
619 }
620
621 #[test]
622 fn modifier_override() {
623 // The latter modifier should override the one before
624 assert_eq!(Plain.fg(Red).fg(Blue), Plain.fg(Blue));
625 assert_eq!(Plain.fg(Red).fg(Blue), Blue.to_style());
626 assert_eq!(Red.fg(Blue), Plain.fg(Blue));
627 assert_eq!(Red.fg(Blue), Blue.to_style());
628 }
629
630 #[test]
631 fn style_and() {
632 let s1 = Style::default().bold().not_underline();
633 let s2 = Style::default().underline();
634 let s3 = Style::default().bold();
635
636 let r1 = Style::default().bold().underline();
637 let r2 = Style::default().bold().not_underline();
638
639 assert_eq!(s2.and(s1), r2);
640 assert_eq!(s2.and(s1).and(s3), r2);
641 assert_eq!(s2.and(s3), r1);
642 }
643}