Minor formatting changes
[tutil.git] / src / crayon.rs
blob016213c9b350822511e6f5480487be805676386b
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> where S: Into<Cow<'a, str>> {
115     fn from(string: S) -> StyledString<'a> {
116         StyledString {
117             string: string.into(),
118             style: Style::default(),
119         }
120     }
123 impl<'a> Deref for StyledString<'a> {
124     type Target = str;
126     fn deref(&self) -> &str {
127         self.string.deref()
128     }
131 /// A `Color` is a specific ANSI colour name which can refer to either the
132 /// foreground or background.
134 /// It can also be a custom value from 0 to 255 via the `Fixed(u8)` variant for
135 /// terminals that have 256-colour. True-colour is also supported via the
136 /// `Rgb(u8, u8, u8)` variant.
138 /// For further reading visit this [Wikipedia page][wp].
140 /// [wp]: https://en.wikipedia.org/wiki/ANSI_escape_code#Colors
141 #[derive(Debug, Clone, Copy, PartialEq)]
142 pub enum Color {
143     /// Foreground code `30`, background code `40`.
144     Black,
146     /// Foreground code `31`, background code `41`.
147     Red,
149     /// Foreground code `32`, background code `42`.
150     Green,
152     /// Foreground code `33`, background code `43`.
153     Yellow,
155     /// Foreground code `34`, background code `44`.
156     Blue,
158     /// Foreground code `35`, background code `45`.
159     Purple,
161     /// Foreground code `36`, background code `46`.
162     Cyan,
164     /// Foreground code `37`, background code `47`.
165     White,
167     /// A value from 0 to 255 for use on terminals that have 256-colour support.
168     ///
169     /// * 0 to 7 are the `Black` through `White` variants. These colours can
170     ///   usually be changed in the terminal emulator.
171     /// * 8 to 15 are brighter versions of the aforementioned colours. These
172     ///   colours can also usually be changed in the terminal emulator,
173     ///   however, it also could be configured to use the original colours and
174     ///   show the text in bold.
175     /// * 16 to 231 contain several palettes of bright colours, arranged in six
176     ///   squares measuring six by six each.
177     /// * 232 to 255 are shades of grey from black to white.
178     ///
179     /// You can view this [colour chart][cc] for a visual representation.
180     /// [cc]: https://upload.wikimedia.org/wikipedia/en/1/15/Xterm_256color_chart.svg
181     Fixed(u8),
183     /// A red value, green value and blue value, each between 0 to 255 (the RGB
184     /// colour model) for use on terminals that have true-colour support.
185     ///
186     /// Please note that true-colour support on many terminal emulators is
187     /// patchy, however a fair share of the most common terminal emulators
188     /// support it, such as:
189     ///
190     /// * [Gnome Terminal][gterminal]
191     /// * [Konsole][konsole]
192     /// * [Terminator][terminator]
193     /// * [iTerm 2][iterm2]
194     ///
195     /// For an up-to-date list of true-colour support visit:
196     /// https://gist.github.com/XVilka/8346728.
197     ///
198     /// [gterminal]: https://wiki.gnome.org/Apps/Terminal
199     /// [konsole]: https://konsole.kde.org/
200     /// [terminator]: http://gnometerminator.blogspot.co.nz
201     /// [iterm2]: https://www.iterm2.com/
202     Rgb(u8, u8, u8),
205 impl Color {
206     /// Convenience method for creating a `StyledString` with the foreground set
207     /// without having to manually create a `Style` or use `<color>.normal().paint()`.
208     pub fn paint<'a, S>(self, string: S) -> StyledString<'a> where S: Into<Cow<'a, str>> {
209         StyledString {
210             string: string.into(),
211             style: self.normal(),
212         }
213     }
215     /// Returns a `Style` with the foreground colour set to this colour.
216     pub fn normal(self) -> Style {
217         Style { foreground: Some(self), .. Style::default() }
218     }
220     /// The same as `Color::normal()`, but also sets the background colour.
221     pub fn on(self, background: Color) -> Style {
222         Style { foreground: Some(self), background: Some(background), .. Style::default() }
223     }
225     /// Returns a `Style` with the 'bold' property set and the foreground colour
226     /// set to this colour.
227     pub fn bold(self) -> Style {
228         Style { foreground: Some(self), bold: true, .. Style::default() }
229     }
231     /// Returns a `Style` with the 'dimmed' property set and the foreground
232     /// colour set to this colour.
233     pub fn dimmed(self) -> Style {
234         Style { foreground: Some(self), dimmed: true, .. Style::default() }
235     }
237     /// Returns a `Style` with the 'italic' property set and the foreground
238     /// colour set to this colour.
239     pub fn italic(self) -> Style {
240         Style { foreground: Some(self), italic: true, .. Style::default() }
241     }
243     /// Returns a `Style` with the 'underline' property set and the foreground
244     /// colour set to this colour.
245     pub fn underline(self) -> Style {
246         Style { foreground: Some(self), underline: true, .. Style::default() }
247     }
249     /// Returns a `Style` with the 'blink' property set and the foreground
250     /// colour set to this colour.
251     pub fn blink(self) -> Style {
252         Style { foreground: Some(self), blink: true, .. Style::default() }
253     }
255     /// Returns a `Style` with the 'reverse' property set and the foreground
256     /// colour set to this colour.
257     pub fn reverse(self) -> Style {
258         Style { foreground: Some(self), reverse: true, .. Style::default() }
259     }
261     /// Returns a `Style` with the 'hidden' property set and the foreground
262     /// colour set to this colour.
263     pub fn hidden(self) -> Style {
264         Style { foreground: Some(self), hidden: true, .. Style::default() }
265     }
267     fn write_foreground_code(&self, f: &mut fmt::Formatter) -> fmt::Result {
268         match *self {
269             Black        => write!(f, "30"),
270             Red          => write!(f, "31"),
271             Green        => write!(f, "32"),
272             Yellow       => write!(f, "33"),
273             Blue         => write!(f, "34"),
274             Purple       => write!(f, "35"),
275             Cyan         => write!(f, "36"),
276             White        => write!(f, "37"),
277             Fixed(n)     => write!(f, "38;5;{}", &n),
278             Rgb(r, g, b) => write!(f, "38;2;{};{};{}", &r, &g, &b),
279         }
280     }
282     fn write_background_code(&self, f: &mut fmt::Formatter) -> fmt::Result {
283         match *self {
284             Black        => write!(f, "40"),
285             Red          => write!(f, "41"),
286             Green        => write!(f, "42"),
287             Yellow       => write!(f, "43"),
288             Blue         => write!(f, "44"),
289             Purple       => write!(f, "45"),
290             Cyan         => write!(f, "46"),
291             White        => write!(f, "47"),
292             Fixed(n)     => write!(f, "48;5;{}", &n),
293             Rgb(r, g, b) => write!(f, "48;2;{};{};{}", &r, &g, &b),
294         }
295     }
298 /// A collection of properties that are used to format a string.
299 #[derive(Debug, Clone, Copy, PartialEq)]
300 pub struct Style {
301     foreground: Option<Color>,
302     background: Option<Color>,
303     bold: bool,
304     dimmed: bool,
305     italic: bool,
306     underline: bool,
307     blink: bool,
308     reverse: bool,
309     hidden: bool,
312 impl Style {
313     /// Creates a new `Style` without any formatting.
314     pub fn new() -> Style {
315         Style::default()
316     }
318     /// Applies the `Style` to a string, yielding a `StyledString`.
319     pub fn paint<'a, S>(self, string: S) -> StyledString<'a> where S: Into<Cow<'a, str>> {
320         StyledString { string: string.into(), style: self }
321     }
323     /// Sets the foreground to the given colour.
324     pub fn foreground(&self, color: Color) -> Style {
325         Style { foreground: Some(color), .. *self }
326     }
328     /// Sets the background to the given colour.
329     pub fn background(&self, color: Color) -> Style {
330         Style { background: Some(color), .. *self }
331     }
333     /// Applies the 'bold' property.
334     pub fn bold(&self) -> Style {
335         Style { bold: true, .. *self }
336     }
338     /// Applies the 'dimmed' property.
339     pub fn dimmed(&self) -> Style {
340         Style { dimmed: true, .. *self }
341     }
343     /// Applies the 'italic' property.
344     pub fn italic(&self) -> Style {
345         Style { italic: true, .. *self }
346     }
348     /// Applies the 'underline' property.
349     pub fn underline(&self) -> Style {
350         Style { underline: true, .. *self }
351     }
353     /// Applies the 'blink' property.
354     pub fn blink(&self) -> Style {
355         Style { blink: true, .. *self }
356     }
358     /// Applies the 'reverse' property.
359     pub fn reverse(&self) -> Style {
360         Style { reverse: true, .. *self }
361     }
363     /// Applies the 'hidden' property.
364     pub fn hidden(&self) -> Style {
365         Style { hidden: true, .. *self }
366     }
368     /// Returns true if this `Style` has no colours or properties set.
369     fn is_plain(self) -> bool {
370         self == Style::default()
371     }
373     /// Write any ANSI escape codes that go before the given text, such as
374     /// colour or style codes.
375     fn write_prefix(&self, f: &mut fmt::Formatter) -> fmt::Result {
376         use std::fmt::Write;
378         if self.is_plain() { return Ok(()); }
380         try!(write!(f, "\x1b["));
381         let mut written_anything = false;
383         {
384             let mut write_char = |c| {
385                 if written_anything { try!(f.write_char(';')); }
386                 written_anything = true;
387                 try!(f.write_char(c));
388                 Ok(())
389             };
391             if self.bold      { try!(write_char('1')); }
392             if self.dimmed    { try!(write_char('2')); }
393             if self.italic    { try!(write_char('3')); }
394             if self.underline { try!(write_char('4')); }
395             if self.blink     { try!(write_char('5')); }
396             if self.reverse   { try!(write_char('6')); }
397             if self.hidden    { try!(write_char('7')); }
398         }
400         if let Some(fg) = self.foreground {
401             if written_anything { try!(f.write_char(';')); }
402             written_anything = true;
404             try!(fg.write_foreground_code(f));
405         }
407         if let Some(bg) = self.background {
408             if written_anything { try!(f.write_char(';')); }
410             try!(bg.write_background_code(f));
411         }
413         try!(f.write_char('m'));
414         Ok(())
415     }
417     /// Write any ANSI escape codes that go after the given text, typically the
418     /// reset code.
419     fn write_suffix(&self, f: &mut fmt::Formatter) -> fmt::Result {
420         if self.is_plain() {
421             return Ok(());
422         } else {
423             write!(f, "\x1b[0m")
424         }
425     }
428 impl Default for Style {
429     fn default() -> Style {
430         Style {
431             foreground: None,
432             background: None,
433             bold: false,
434             dimmed: false,
435             italic: false,
436             underline: false,
437             blink: false,
438             reverse: false,
439             hidden: false,
440         }
441     }
444 #[cfg(test)]
445 mod test {
446     use super::*;
447     use super::Color::*;
449     // Convenience macro for creating test cases.
450     macro_rules! test {
451         ($name: ident: $style: expr; $input: expr => $result: expr) => {
452             #[test]
453             fn $name() {
454                 assert_eq!($style.paint($input).to_string(), $result.to_string())
455             }
456         }
457     }
459     test!(plain:             Style::default(); "TEST" => "TEST");
460     test!(black:             Black;            "TEST" => "\x1b[30mTEST\x1b[0m");
461     test!(black_background:  White.on(Black);  "TEST" => "\x1b[37;40mTEST\x1b[0m");
462     test!(red:               Red;              "TEST" => "\x1b[31mTEST\x1b[0m");
463     test!(red_background:    Black.on(Red);    "TEST" => "\x1b[30;41mTEST\x1b[0m");
464     test!(green:             Green;            "TEST" => "\x1b[32mTEST\x1b[0m");
465     test!(green_background:  Black.on(Green);  "TEST" => "\x1b[30;42mTEST\x1b[0m");
466     test!(yellow:            Yellow;           "TEST" => "\x1b[33mTEST\x1b[0m");
467     test!(yellow_background: Black.on(Yellow); "TEST" => "\x1b[30;43mTEST\x1b[0m");
468     test!(blue:              Blue;             "TEST" => "\x1b[34mTEST\x1b[0m");
469     test!(blue_background:   Black.on(Blue);   "TEST" => "\x1b[30;44mTEST\x1b[0m");
470     test!(purple:            Purple;           "TEST" => "\x1b[35mTEST\x1b[0m");
471     test!(purple_background: Black.on(Purple); "TEST" => "\x1b[30;45mTEST\x1b[0m");
472     test!(cyan:              Cyan;             "TEST" => "\x1b[36mTEST\x1b[0m");
473     test!(cyan_background:   Black.on(Cyan);   "TEST" => "\x1b[30;46mTEST\x1b[0m");
474     test!(white:             White;            "TEST" => "\x1b[37mTEST\x1b[0m");
475     test!(white_background:  Black.on(White);  "TEST" => "\x1b[30;47mTEST\x1b[0m");
476     test!(fixed:             Fixed(220);       "TEST" => "\x1b[38;5;220mTEST\x1b[0m");
477     test!(fixed_background:  Fixed(220).on(Fixed(245));
478           "TEST" => "\x1b[38;5;220;48;5;245mTEST\x1b[0m");
479     test!(rgb: Rgb(105, 245, 238);
480           "TEST" => "\x1b[38;2;105;245;238mTEST\x1b[0m");
481     test!(rgb_background: Black.on(Rgb(100, 245, 238));
482           "TEST" => "\x1b[30;48;2;100;245;238mTEST\x1b[0m");
484     test!(bold:      Style::new().bold();      "TEST" => "\x1b[1mTEST\x1b[0m");
485     test!(dimmed:    Style::new().dimmed();    "TEST" => "\x1b[2mTEST\x1b[0m");
486     test!(italic:    Style::new().italic();    "TEST" => "\x1b[3mTEST\x1b[0m");
487     test!(underline: Style::new().underline(); "TEST" => "\x1b[4mTEST\x1b[0m");
488     test!(blink:     Style::new().blink();     "TEST" => "\x1b[5mTEST\x1b[0m");
489     test!(reverse:   Style::new().reverse();   "TEST" => "\x1b[6mTEST\x1b[0m");
490     test!(hidden:    Style::new().hidden();    "TEST" => "\x1b[7mTEST\x1b[0m");