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(&copy));
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}