Remove unneeded import
[tutil.git] / src / crayon.rs
blob0fac9deacbafa8f413c1b82cc69da477c10b31c2
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>>
117     fn from(string: S) -> StyledString<'a> {
118         StyledString { string: string.into(), style: Style::default() }
119     }
122 impl<'a> Deref for StyledString<'a> {
123     type Target = str;
125     fn deref(&self) -> &str {
126         self.string.deref()
127     }
130 /// A `Color` is a specific ANSI colour name which can refer to either the
131 /// foreground or background.
133 /// It can also be a custom value from 0 to 255 via the `Fixed(u8)` variant for
134 /// terminals that have 256-colour. True-colour is also supported via the
135 /// `Rgb(u8, u8, u8)` variant.
137 /// For further reading visit this [Wikipedia page][wp].
139 /// [wp]: https://en.wikipedia.org/wiki/ANSI_escape_code#Colors
140 #[derive(Debug, Clone, Copy, PartialEq)]
141 pub enum Color {
142     /// Foreground code `30`, background code `40`.
143     Black,
145     /// Foreground code `31`, background code `41`.
146     Red,
148     /// Foreground code `32`, background code `42`.
149     Green,
151     /// Foreground code `33`, background code `43`.
152     Yellow,
154     /// Foreground code `34`, background code `44`.
155     Blue,
157     /// Foreground code `35`, background code `45`.
158     Purple,
160     /// Foreground code `36`, background code `46`.
161     Cyan,
163     /// Foreground code `37`, background code `47`.
164     White,
166     /// A value from 0 to 255 for use on terminals that have 256-colour support.
167     ///
168     /// * 0 to 7 are the `Black` through `White` variants. These colours can
169     ///   usually be changed in the terminal emulator.
170     /// * 8 to 15 are brighter versions of the aforementioned colours. These
171     ///   colours can also usually be changed in the terminal emulator,
172     ///   however, it also could be configured to use the original colours and
173     ///   show the text in bold.
174     /// * 16 to 231 contain several palettes of bright colours, arranged in six
175     ///   squares measuring six by six each.
176     /// * 232 to 255 are shades of grey from black to white.
177     ///
178     /// You can view this [colour chart][cc] for a visual representation.
179     /// [cc]: https://upload.wikimedia.org/wikipedia/en/1/15/Xterm_256color_chart.svg
180     Fixed(u8),
182     /// A red value, green value and blue value, each between 0 to 255 (the RGB
183     /// colour model) for use on terminals that have true-colour support.
184     ///
185     /// Please note that true-colour support on many terminal emulators is
186     /// patchy, however a fair share of the most common terminal emulators
187     /// support it, such as:
188     ///
189     /// * [Gnome Terminal][gterminal]
190     /// * [Konsole][konsole]
191     /// * [Terminator][terminator]
192     /// * [iTerm 2][iterm2]
193     ///
194     /// For an up-to-date list of true-colour support visit:
195     /// https://gist.github.com/XVilka/8346728.
196     ///
197     /// [gterminal]: https://wiki.gnome.org/Apps/Terminal
198     /// [konsole]: https://konsole.kde.org/
199     /// [terminator]: http://gnometerminator.blogspot.co.nz
200     /// [iterm2]: https://www.iterm2.com/
201     Rgb(u8, u8, u8),
204 impl Color {
205     /// Convenience method for creating a `StyledString` with the foreground set
206     /// without having to manually create a `Style` or use `<color>.normal().paint()`.
207     pub fn paint<'a, S>(self, string: S) -> StyledString<'a>
208         where S: Into<Cow<'a, str>>
209     {
210         StyledString { string: string.into(), style: self.normal() }
211     }
213     /// Returns a `Style` with the foreground colour set to this colour.
214     pub fn normal(self) -> Style {
215         Style { foreground: Some(self), ..Style::default() }
216     }
218     /// The same as `Color::normal()`, but also sets the background colour.
219     pub fn on(self, background: Color) -> Style {
220         Style { foreground: Some(self), background: Some(background), ..Style::default() }
221     }
223     /// Returns a `Style` with the 'bold' property set and the foreground colour
224     /// set to this colour.
225     pub fn bold(self) -> Style {
226         Style { foreground: Some(self), bold: true, ..Style::default() }
227     }
229     /// Returns a `Style` with the 'dimmed' property set and the foreground
230     /// colour set to this colour.
231     pub fn dimmed(self) -> Style {
232         Style { foreground: Some(self), dimmed: true, ..Style::default() }
233     }
235     /// Returns a `Style` with the 'italic' property set and the foreground
236     /// colour set to this colour.
237     pub fn italic(self) -> Style {
238         Style { foreground: Some(self), italic: true, ..Style::default() }
239     }
241     /// Returns a `Style` with the 'underline' property set and the foreground
242     /// colour set to this colour.
243     pub fn underline(self) -> Style {
244         Style { foreground: Some(self), underline: true, ..Style::default() }
245     }
247     /// Returns a `Style` with the 'blink' property set and the foreground
248     /// colour set to this colour.
249     pub fn blink(self) -> Style {
250         Style { foreground: Some(self), blink: true, ..Style::default() }
251     }
253     /// Returns a `Style` with the 'reverse' property set and the foreground
254     /// colour set to this colour.
255     pub fn reverse(self) -> Style {
256         Style { foreground: Some(self), reverse: true, ..Style::default() }
257     }
259     /// Returns a `Style` with the 'hidden' property set and the foreground
260     /// colour set to this colour.
261     pub fn hidden(self) -> Style {
262         Style { foreground: Some(self), hidden: true, ..Style::default() }
263     }
265     fn write_foreground_code(&self, f: &mut fmt::Formatter) -> fmt::Result {
266         match *self {
267             Black => write!(f, "30"),
268             Red => write!(f, "31"),
269             Green => write!(f, "32"),
270             Yellow => write!(f, "33"),
271             Blue => write!(f, "34"),
272             Purple => write!(f, "35"),
273             Cyan => write!(f, "36"),
274             White => write!(f, "37"),
275             Fixed(n) => write!(f, "38;5;{}", &n),
276             Rgb(r, g, b) => write!(f, "38;2;{};{};{}", &r, &g, &b),
277         }
278     }
280     fn write_background_code(&self, f: &mut fmt::Formatter) -> fmt::Result {
281         match *self {
282             Black => write!(f, "40"),
283             Red => write!(f, "41"),
284             Green => write!(f, "42"),
285             Yellow => write!(f, "43"),
286             Blue => write!(f, "44"),
287             Purple => write!(f, "45"),
288             Cyan => write!(f, "46"),
289             White => write!(f, "47"),
290             Fixed(n) => write!(f, "48;5;{}", &n),
291             Rgb(r, g, b) => write!(f, "48;2;{};{};{}", &r, &g, &b),
292         }
293     }
296 /// A collection of properties that are used to format a string.
297 #[derive(Debug, Clone, Copy, PartialEq)]
298 pub struct Style {
299     foreground: Option<Color>,
300     background: Option<Color>,
301     bold: bool,
302     dimmed: bool,
303     italic: bool,
304     underline: bool,
305     blink: bool,
306     reverse: bool,
307     hidden: bool,
310 impl Style {
311     /// Creates a new `Style` without any formatting.
312     pub fn new() -> Style {
313         Style::default()
314     }
316     /// Applies the `Style` to a string, yielding a `StyledString`.
317     pub fn paint<'a, S>(self, string: S) -> StyledString<'a>
318         where S: Into<Cow<'a, str>>
319     {
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() {
379             return Ok(());
380         }
382         try!(write!(f, "\x1b["));
383         let mut written_anything = false;
385         {
386             let mut write_char = |c| {
387                 if written_anything {
388                     try!(f.write_char(';'));
389                 }
390                 written_anything = true;
391                 try!(f.write_char(c));
392                 Ok(())
393             };
395             if self.bold { try!(write_char('1')); }
396             if self.dimmed { try!(write_char('2')); }
397             if self.italic { try!(write_char('3')); }
398             if self.underline { try!(write_char('4')); }
399             if self.blink { try!(write_char('5')); }
400             if self.reverse { try!(write_char('6')); }
401             if self.hidden { try!(write_char('7')); }
402         }
404         if let Some(fg) = self.foreground {
405             if written_anything { try!(f.write_char(';')); }
406             written_anything = true;
408             try!(fg.write_foreground_code(f));
409         }
411         if let Some(bg) = self.background {
412             if written_anything { try!(f.write_char(';')); }
414             try!(bg.write_background_code(f));
415         }
417         try!(f.write_char('m'));
418         Ok(())
419     }
421     /// Write any ANSI escape codes that go after the given text, typically the
422     /// reset code.
423     fn write_suffix(&self, f: &mut fmt::Formatter) -> fmt::Result {
424         if self.is_plain() {
425             return Ok(());
426         } else {
427             write!(f, "\x1b[0m")
428         }
429     }
432 impl Default for Style {
433     fn default() -> Style {
434         Style {
435             foreground: None,
436             background: None,
437             bold: false,
438             dimmed: false,
439             italic: false,
440             underline: false,
441             blink: false,
442             reverse: false,
443             hidden: false,
444         }
445     }
448 #[cfg(test)]
449 mod test {
450     use super::*;
451     use super::Color::*;
453     // Convenience macro for creating test cases.
454     macro_rules! test {
455         ($name: ident: $style: expr; $input: expr => $result: expr) => {
456             #[test]
457             fn $name() {
458                 assert_eq!($style.paint($input).to_string(), $result.to_string())
459             }
460         }
461     }
463     test!(plain:             Style::default(); "TEST" => "TEST");
464     test!(black:             Black;            "TEST" => "\x1b[30mTEST\x1b[0m");
465     test!(black_background:  White.on(Black);  "TEST" => "\x1b[37;40mTEST\x1b[0m");
466     test!(red:               Red;              "TEST" => "\x1b[31mTEST\x1b[0m");
467     test!(red_background:    Black.on(Red);    "TEST" => "\x1b[30;41mTEST\x1b[0m");
468     test!(green:             Green;            "TEST" => "\x1b[32mTEST\x1b[0m");
469     test!(green_background:  Black.on(Green);  "TEST" => "\x1b[30;42mTEST\x1b[0m");
470     test!(yellow:            Yellow;           "TEST" => "\x1b[33mTEST\x1b[0m");
471     test!(yellow_background: Black.on(Yellow); "TEST" => "\x1b[30;43mTEST\x1b[0m");
472     test!(blue:              Blue;             "TEST" => "\x1b[34mTEST\x1b[0m");
473     test!(blue_background:   Black.on(Blue);   "TEST" => "\x1b[30;44mTEST\x1b[0m");
474     test!(purple:            Purple;           "TEST" => "\x1b[35mTEST\x1b[0m");
475     test!(purple_background: Black.on(Purple); "TEST" => "\x1b[30;45mTEST\x1b[0m");
476     test!(cyan:              Cyan;             "TEST" => "\x1b[36mTEST\x1b[0m");
477     test!(cyan_background:   Black.on(Cyan);   "TEST" => "\x1b[30;46mTEST\x1b[0m");
478     test!(white:             White;            "TEST" => "\x1b[37mTEST\x1b[0m");
479     test!(white_background:  Black.on(White);  "TEST" => "\x1b[30;47mTEST\x1b[0m");
480     test!(fixed:             Fixed(220);       "TEST" => "\x1b[38;5;220mTEST\x1b[0m");
481     test!(fixed_background:  Fixed(220).on(Fixed(245));
482           "TEST" => "\x1b[38;5;220;48;5;245mTEST\x1b[0m");
483     test!(rgb: Rgb(105, 245, 238);
484           "TEST" => "\x1b[38;2;105;245;238mTEST\x1b[0m");
485     test!(rgb_background: Black.on(Rgb(100, 245, 238));
486           "TEST" => "\x1b[30;48;2;100;245;238mTEST\x1b[0m");
488     test!(bold:      Style::new().bold();      "TEST" => "\x1b[1mTEST\x1b[0m");
489     test!(dimmed:    Style::new().dimmed();    "TEST" => "\x1b[2mTEST\x1b[0m");
490     test!(italic:    Style::new().italic();    "TEST" => "\x1b[3mTEST\x1b[0m");
491     test!(underline: Style::new().underline(); "TEST" => "\x1b[4mTEST\x1b[0m");
492     test!(blink:     Style::new().blink();     "TEST" => "\x1b[5mTEST\x1b[0m");
493     test!(reverse:   Style::new().reverse();   "TEST" => "\x1b[6mTEST\x1b[0m");
494     test!(hidden:    Style::new().hidden();    "TEST" => "\x1b[7mTEST\x1b[0m");