Update the changelog with the latest changes
[tutil.git] / src / crayon.rs
blob03ba4722219d1c74778a4c1cc0d4a61c3ab219c7
1 // This Source Code Form is subject to the terms of the Mozilla Public
2 // License, v. 2.0. If a copy of the MPL was not distributed with this
3 // file, You can obtain one at http://mozilla.org/MPL/2.0/.
5 //! A module for handling terminal output styling.
6 //!
7 //! It would be fair to say that this module ~~is a rip off of~~ is *heavily*
8 //! influenced by [ogham][ogham]'s [ansi_term][ansiterm] crate and
9 //! [peter-murach][peter]'s [Pastel][pastel] library. However if there is
10 //! something original of mine in here it would be true-colour support at the
11 //! very least.
12 //!
13 //! # OS Support
14 //!
15 //! * Linux
16 //! * OS X
17 //! * FreeBSD
18 //!
19 //! Most other POSIX/*nix systems will probably work as well.
20 //!
21 //! Windows support is planned.
22 //!
23 //! # Basic Usage
24 //!
25 //! The two main data structures in this module are `Style`, which holds
26 //! stylistic information such as the foreground colour, the background colour
27 //! and other properties such as if the text should be bold, blinking, etc. and
28 //! `ANSIString`, which is string paired with a `Style`.
29 //!
30 //! In order to format a string, call the `paint()` method on a `Style` or
31 //! `Color`. For example, here is how you get red text and blue text:
32 //!
33 //! ```
34 //! use tutil::crayon::Color::{Red, Blue};
35 //!
36 //! println!("{}", Red.paint("Hello world in red!"));
37 //! println!("{}", Blue.paint("Hello world in blue!"));
38 //! ```
39 //!
40 //! It is worth noting that `paint()` does not actually return a string with the
41 //! escape codes surrounding it, but instead returns a `StyledString` that has
42 //! an implementation of `Display` that will return the escape codes as well as
43 //! the string when formatted.
44 //!
45 //! In the case that you *do* want the escape codes, you can convert the
46 //! `StyledString` to a string:
47 //!
48 //! ```
49 //! use std::string::ToString;
50 //! use tutil::crayon::Color::Blue;
51 //!
52 //! let string = Blue.paint("Hello!").to_string(); // => "\x1b[34mTEST\x1b[0m"
53 //! ```
54 //!
55 //! # Advanced Usage
56 //!
57 //! For complex styling you can construct a `Style` object rather than operating
58 //! directly on a `Color`:
59 //!
60 //! ```
61 //! use tutil::crayon::Style;
62 //! use tutil::crayon::Color::{Red, Blue};
63 //!
64 //! // Red blinking text on a black background:
65 //! println!("This will be {} and this will be {}.",
66 //!          Style::new().foreground(Red).bold().paint("red and bold"),
67 //!          Style::new().foreground(Blue).italic().paint("blue and italic"));
68 //! ```
69 //!
70 //! The same styling methods that you can use on a `Style` have been implemented
71 //! on the `Color` struct, allowing you to skip creating an empty `Style` with
72 //! `Style::new()`:
73 //!
74 //! ```
75 //! use tutil::crayon::Color::{Red, Blue};
76 //!
77 //! // Red blinking text on a black background:
78 //! println!("This will be {} and this will be {}.",
79 //!          Red.bold().paint("red and bold"),
80 //!          Blue.italic().paint("blue and italic"));
81 //! ```
82 //!
83 //! [ogham]: https://github.com/ogham
84 //! [ansiterm]: https://github.com/ogham/rust-ansi-term
85 //! [peter]: https://github.com/peter-murach
86 //! [pastel]: https://github.com/peter-murach/pastel
88 use std::fmt;
89 use std::ops::Deref;
90 use std::borrow::Cow;
91 use std::default::Default;
93 use self::Color::*;
95 /// A string coupled with a `Style` in order to display it in a terminal.
96 ///
97 /// It can be turned into a string with the `.to_string()` method.
98 #[derive(Debug, Clone, PartialEq)]
99 pub struct StyledString<'a> {
100     string: Cow<'a, str>,
101     style: Style,
104 impl<'a> fmt::Display for StyledString<'a> {
105     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
106         // TODO: Convert the `try!()` calls to the `?` operator once it is
107         //       stable.
108         try!(self.style.write_prefix(f));
109         try!(write!(f, "{}", self.string));
110         self.style.write_suffix(f)
111     }
114 impl<'a, S> From<S> for StyledString<'a>
115 where S: Into<Cow<'a, str>> {
116     fn from(string: S) -> StyledString<'a> {
117         StyledString {
118             string: string.into(),
119             style: Style::default(),
120         }
121     }
124 impl<'a> Deref for StyledString<'a> {
125     type Target = str;
127     fn deref(&self) -> &str {
128         self.string.deref()
129     }
132 /// A `Color` is a specific ANSI colour name which can refer to either the
133 /// foreground or background.
135 /// It can also be a custom value from 0 to 255 via the `Fixed(u8)` variant for
136 /// terminals that have 256-colour. True-colour is also supported via the
137 /// `Rgb(u8, u8, u8)` variant.
139 /// For further reading visit this [Wikipedia page][wp].
141 /// [wp]: https://en.wikipedia.org/wiki/ANSI_escape_code#Colors
142 #[derive(Debug, Clone, Copy, PartialEq)]
143 pub enum Color {
144     /// Foreground code `30`, background code `40`.
145     Black,
147     /// Foreground code `31`, background code `41`.
148     Red,
150     /// Foreground code `32`, background code `42`.
151     Green,
153     /// Foreground code `33`, background code `43`.
154     Yellow,
156     /// Foreground code `34`, background code `44`.
157     Blue,
159     /// Foreground code `35`, background code `45`.
160     Purple,
162     /// Foreground code `36`, background code `46`.
163     Cyan,
165     /// Foreground code `37`, background code `47`.
166     White,
168     /// A value from 0 to 255 for use on terminals that have 256-colour support.
169     ///
170     /// * 0 to 7 are the `Black` through `White` variants. These colours can
171     ///   usually be changed in the terminal emulator.
172     /// * 8 to 15 are brighter versions of the aforementioned colours. These
173     ///   colours can also usually be changed in the terminal emulator,
174     ///   however, it also could be configured to use the original colours and
175     ///   show the text in bold.
176     /// * 16 to 231 contain several palettes of bright colours, arranged in six
177     ///   squares measuring six by six each.
178     /// * 232 to 255 are shades of grey from black to white.
179     ///
180     /// You can view this [colour chart][cc] for a visual representation.
181     /// [cc]: https://upload.wikimedia.org/wikipedia/en/1/15/Xterm_256color_chart.svg
182     Fixed(u8),
184     /// A red value, green value and blue value, each between 0 to 255 (the RGB
185     /// colour model) for use on terminals that have true-colour support.
186     ///
187     /// Please note that true-colour support on many terminal emulators is
188     /// patchy, however a fair share of the most common terminal emulators
189     /// support it, such as:
190     ///
191     /// * [Gnome Terminal][gterminal]
192     /// * [Konsole][konsole]
193     /// * [Terminator][terminator]
194     /// * [iTerm 2][iterm2]
195     ///
196     /// For an up-to-date list of true-colour support visit:
197     /// https://gist.github.com/XVilka/8346728.
198     ///
199     /// [gterminal]: https://wiki.gnome.org/Apps/Terminal
200     /// [konsole]: https://konsole.kde.org/
201     /// [terminator]: http://gnometerminator.blogspot.co.nz
202     /// [iterm2]: https://www.iterm2.com/
203     Rgb(u8, u8, u8),
206 impl Color {
207     /// Convenience method for creating a `StyledString` with the foreground set
208     /// without having to manually create a `Style` or use `<color>.normal().paint()`.
209     pub fn paint<'a, S>(self, string: S) -> StyledString<'a>
210     where S: Into<Cow<'a, str>> {
211         StyledString {
212             string: string.into(),
213             style: self.normal(),
214         }
215     }
217     /// Returns a `Style` with the foreground colour set to this colour.
218     pub fn normal(self) -> Style {
219         Style { foreground: Some(self), .. Style::default() }
220     }
222     /// The same as `Color::normal()`, but also sets the background colour.
223     pub fn on(self, background: Color) -> Style {
224         Style { foreground: Some(self), background: Some(background), .. Style::default() }
225     }
227     /// Returns a `Style` with the 'bold' property set and the foreground colour
228     /// set to this colour.
229     pub fn bold(self) -> Style {
230         Style { foreground: Some(self), bold: true, .. Style::default() }
231     }
233     /// Returns a `Style` with the 'dimmed' property set and the foreground
234     /// colour set to this colour.
235     pub fn dimmed(self) -> Style {
236         Style { foreground: Some(self), dimmed: true, .. Style::default() }
237     }
239     /// Returns a `Style` with the 'italic' property set and the foreground
240     /// colour set to this colour.
241     pub fn italic(self) -> Style {
242         Style { foreground: Some(self), italic: true, .. Style::default() }
243     }
245     /// Returns a `Style` with the 'underline' property set and the foreground
246     /// colour set to this colour.
247     pub fn underline(self) -> Style {
248         Style { foreground: Some(self), underline: true, .. Style::default() }
249     }
251     /// Returns a `Style` with the 'blink' property set and the foreground
252     /// colour set to this colour.
253     pub fn blink(self) -> Style {
254         Style { foreground: Some(self), blink: true, .. Style::default() }
255     }
257     /// Returns a `Style` with the 'reverse' property set and the foreground
258     /// colour set to this colour.
259     pub fn reverse(self) -> Style {
260         Style { foreground: Some(self), reverse: true, .. Style::default() }
261     }
263     /// Returns a `Style` with the 'hidden' property set and the foreground
264     /// colour set to this colour.
265     pub fn hidden(self) -> Style {
266         Style { foreground: Some(self), hidden: true, .. Style::default() }
267     }
269     fn write_foreground_code(&self, f: &mut fmt::Formatter) -> fmt::Result {
270         match *self {
271             Black        => write!(f, "30"),
272             Red          => write!(f, "31"),
273             Green        => write!(f, "32"),
274             Yellow       => write!(f, "33"),
275             Blue         => write!(f, "34"),
276             Purple       => write!(f, "35"),
277             Cyan         => write!(f, "36"),
278             White        => write!(f, "37"),
279             Fixed(n)     => write!(f, "38;5;{}", &n),
280             Rgb(r, g, b) => write!(f, "38;2;{};{};{}", &r, &g, &b),
281         }
282     }
284     fn write_background_code(&self, f: &mut fmt::Formatter) -> fmt::Result {
285         match *self {
286             Black        => write!(f, "40"),
287             Red          => write!(f, "41"),
288             Green        => write!(f, "42"),
289             Yellow       => write!(f, "43"),
290             Blue         => write!(f, "44"),
291             Purple       => write!(f, "45"),
292             Cyan         => write!(f, "46"),
293             White        => write!(f, "47"),
294             Fixed(n)     => write!(f, "48;5;{}", &n),
295             Rgb(r, g, b) => write!(f, "48;2;{};{};{}", &r, &g, &b),
296         }
297     }
300 /// A collection of properties that are used to format a string.
301 #[derive(Debug, Clone, Copy, PartialEq)]
302 pub struct Style {
303     foreground: Option<Color>,
304     background: Option<Color>,
305     bold: bool,
306     dimmed: bool,
307     italic: bool,
308     underline: bool,
309     blink: bool,
310     reverse: bool,
311     hidden: bool,
314 impl Style {
315     /// Creates a new `Style` without any formatting.
316     pub fn new() -> Style {
317         Style::default()
318     }
320     /// Applies the `Style` to a string, yielding a `StyledString`.
321     pub fn paint<'a, S>(self, string: S) -> StyledString<'a>
322     where S: Into<Cow<'a, str>> {
323         StyledString { string: string.into(), style: self }
324     }
326     /// Sets the foreground to the given colour.
327     pub fn foreground(&self, color: Color) -> Style {
328         Style { foreground: Some(color), .. *self }
329     }
331     /// Sets the background to the given colour.
332     pub fn background(&self, color: Color) -> Style {
333         Style { background: Some(color), .. *self }
334     }
336     /// Applies the 'bold' property.
337     pub fn bold(&self) -> Style {
338         Style { bold: true, .. *self }
339     }
341     /// Applies the 'dimmed' property.
342     pub fn dimmed(&self) -> Style {
343         Style { dimmed: true, .. *self }
344     }
346     /// Applies the 'italic' property.
347     pub fn italic(&self) -> Style {
348         Style { italic: true, .. *self }
349     }
351     /// Applies the 'underline' property.
352     pub fn underline(&self) -> Style {
353         Style { underline: true, .. *self }
354     }
356     /// Applies the 'blink' property.
357     pub fn blink(&self) -> Style {
358         Style { blink: true, .. *self }
359     }
361     /// Applies the 'reverse' property.
362     pub fn reverse(&self) -> Style {
363         Style { reverse: true, .. *self }
364     }
366     /// Applies the 'hidden' property.
367     pub fn hidden(&self) -> Style {
368         Style { hidden: true, .. *self }
369     }
371     /// Returns true if this `Style` has no colours or properties set.
372     fn is_plain(self) -> bool {
373         self == Style::default()
374     }
376     /// Write any ANSI escape codes that go before the given text, such as
377     /// colour or style codes.
378     fn write_prefix(&self, f: &mut fmt::Formatter) -> fmt::Result {
379         use std::fmt::Write;
381         if self.is_plain() { return Ok(()); }
383         try!(write!(f, "\x1b["));
384         let mut written_anything = false;
386         {
387             let mut write_char = |c| {
388                 if written_anything { try!(f.write_char(';')); }
389                 written_anything = true;
390                 try!(f.write_char(c));
391                 Ok(())
392             };
394             if self.bold      { try!(write_char('1')); }
395             if self.dimmed    { try!(write_char('2')); }
396             if self.italic    { try!(write_char('3')); }
397             if self.underline { try!(write_char('4')); }
398             if self.blink     { try!(write_char('5')); }
399             if self.reverse   { try!(write_char('6')); }
400             if self.hidden    { try!(write_char('7')); }
401         }
403         if let Some(fg) = self.foreground {
404             if written_anything { try!(f.write_char(';')); }
405             written_anything = true;
407             try!(fg.write_foreground_code(f));
408         }
410         if let Some(bg) = self.background {
411             if written_anything { try!(f.write_char(';')); }
413             try!(bg.write_background_code(f));
414         }
416         try!(f.write_char('m'));
417         Ok(())
418     }
420     /// Write any ANSI escape codes that go after the given text, typically the
421     /// reset code.
422     fn write_suffix(&self, f: &mut fmt::Formatter) -> fmt::Result {
423         if self.is_plain() {
424             return Ok(());
425         } else {
426             write!(f, "\x1b[0m")
427         }
428     }
431 impl Default for Style {
432     fn default() -> Style {
433         Style {
434             foreground: None,
435             background: None,
436             bold: false,
437             dimmed: false,
438             italic: false,
439             underline: false,
440             blink: false,
441             reverse: false,
442             hidden: false,
443         }
444     }
447 #[cfg(test)]
448 mod test {
449     use super::*;
450     use super::Color::*;
452     // Convenience macro for creating test cases.
453     macro_rules! test {
454         ($name: ident: $style: expr; $input: expr => $result: expr) => {
455             #[test]
456             fn $name() {
457                 assert_eq!($style.paint($input).to_string(), $result.to_string())
458             }
459         }
460     }
462     test!(plain:             Style::default(); "TEST" => "TEST");
463     test!(black:             Black;            "TEST" => "\x1b[30mTEST\x1b[0m");
464     test!(black_background:  White.on(Black);  "TEST" => "\x1b[37;40mTEST\x1b[0m");
465     test!(red:               Red;              "TEST" => "\x1b[31mTEST\x1b[0m");
466     test!(red_background:    Black.on(Red);    "TEST" => "\x1b[30;41mTEST\x1b[0m");
467     test!(green:             Green;            "TEST" => "\x1b[32mTEST\x1b[0m");
468     test!(green_background:  Black.on(Green);  "TEST" => "\x1b[30;42mTEST\x1b[0m");
469     test!(yellow:            Yellow;           "TEST" => "\x1b[33mTEST\x1b[0m");
470     test!(yellow_background: Black.on(Yellow); "TEST" => "\x1b[30;43mTEST\x1b[0m");
471     test!(blue:              Blue;             "TEST" => "\x1b[34mTEST\x1b[0m");
472     test!(blue_background:   Black.on(Blue);   "TEST" => "\x1b[30;44mTEST\x1b[0m");
473     test!(purple:            Purple;           "TEST" => "\x1b[35mTEST\x1b[0m");
474     test!(purple_background: Black.on(Purple); "TEST" => "\x1b[30;45mTEST\x1b[0m");
475     test!(cyan:              Cyan;             "TEST" => "\x1b[36mTEST\x1b[0m");
476     test!(cyan_background:   Black.on(Cyan);   "TEST" => "\x1b[30;46mTEST\x1b[0m");
477     test!(white:             White;            "TEST" => "\x1b[37mTEST\x1b[0m");
478     test!(white_background:  Black.on(White);  "TEST" => "\x1b[30;47mTEST\x1b[0m");
479     test!(fixed:             Fixed(220);       "TEST" => "\x1b[38;5;220mTEST\x1b[0m");
480     test!(fixed_background:  Fixed(220).on(Fixed(245));
481           "TEST" => "\x1b[38;5;220;48;5;245mTEST\x1b[0m");
482     test!(rgb: Rgb(105, 245, 238);
483           "TEST" => "\x1b[38;2;105;245;238mTEST\x1b[0m");
484     test!(rgb_background: Black.on(Rgb(100, 245, 238));
485           "TEST" => "\x1b[30;48;2;100;245;238mTEST\x1b[0m");
487     test!(bold:      Style::new().bold();      "TEST" => "\x1b[1mTEST\x1b[0m");
488     test!(dimmed:    Style::new().dimmed();    "TEST" => "\x1b[2mTEST\x1b[0m");
489     test!(italic:    Style::new().italic();    "TEST" => "\x1b[3mTEST\x1b[0m");
490     test!(underline: Style::new().underline(); "TEST" => "\x1b[4mTEST\x1b[0m");
491     test!(blink:     Style::new().blink();     "TEST" => "\x1b[5mTEST\x1b[0m");
492     test!(reverse:   Style::new().reverse();   "TEST" => "\x1b[6mTEST\x1b[0m");
493     test!(hidden:    Style::new().hidden();    "TEST" => "\x1b[7mTEST\x1b[0m");