Import yansi-term 0.1.2 upstream upstream/0.1.2
authorDongHun Kwak <dh0128.kwak@samsung.com>
Mon, 12 Jun 2023 05:53:09 +0000 (14:53 +0900)
committerDongHun Kwak <dh0128.kwak@samsung.com>
Mon, 12 Jun 2023 05:53:09 +0000 (14:53 +0900)
19 files changed:
.cargo_vcs_info.json [new file with mode: 0644]
.github/workflows/checks.yml [new file with mode: 0644]
.github/workflows/linux.yml [new file with mode: 0644]
.github/workflows/osx.yml [new file with mode: 0644]
.gitignore [new file with mode: 0644]
.travis.yml [new file with mode: 0644]
Cargo.toml [new file with mode: 0644]
Cargo.toml.orig [new file with mode: 0644]
LICENCE [new file with mode: 0644]
README.md [new file with mode: 0644]
examples/256_colours.rs [new file with mode: 0644]
examples/basic_colours.rs [new file with mode: 0644]
examples/overwrite.rs [new file with mode: 0644]
examples/rgb_colours.rs [new file with mode: 0644]
src/ansi.rs [new file with mode: 0644]
src/display.rs [new file with mode: 0644]
src/lib.rs [new file with mode: 0644]
src/style.rs [new file with mode: 0644]
src/windows.rs [new file with mode: 0644]

diff --git a/.cargo_vcs_info.json b/.cargo_vcs_info.json
new file mode 100644 (file)
index 0000000..09e087f
--- /dev/null
@@ -0,0 +1,5 @@
+{
+  "git": {
+    "sha1": "b30235f332c1861316b3b812643441942d84fe1c"
+  }
+}
diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml
new file mode 100644 (file)
index 0000000..f521a9b
--- /dev/null
@@ -0,0 +1,41 @@
+name: Checks
+
+on:
+  push:
+  pull_request:
+  schedule: [cron: "40 1 * * *"]
+
+jobs:
+  clippy:
+    name: Clippy
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@v2
+      - uses: actions-rs/toolchain@v1
+        with:
+            toolchain: nightly
+            components: clippy
+            override: true
+      - uses: actions-rs/clippy-check@v1
+        with:
+          token: ${{ secrets.GITHUB_TOKEN }}
+          args: --all-features
+
+  fmt:
+    name: Rustfmt
+    runs-on: ubuntu-latest
+    strategy:
+      matrix:
+        rust:
+          - stable
+    steps:
+      - uses: actions/checkout@v2
+      - uses: actions-rs/toolchain@v1
+        with:
+          profile: minimal
+          toolchain: ${{ matrix.rust }}
+          override: true
+      - uses: actions-rs/cargo@v1
+        with:
+          command: fmt
+          args: --all -- --check
diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml
new file mode 100644 (file)
index 0000000..43bb404
--- /dev/null
@@ -0,0 +1,63 @@
+name: CI (Linux)
+
+on:
+  push:
+  pull_request:
+  schedule: [cron: "40 1 * * *"]
+
+jobs:
+  build_and_test:
+    strategy:
+      fail-fast: false
+      matrix:
+        version:
+          - 1.42.0 # MSRV
+          - stable
+          - nightly
+
+    name: ${{ matrix.version }} - x86_64-unknown-linux-gnu
+    runs-on: ubuntu-latest
+
+    steps:
+      - uses: actions/checkout@master
+
+      - name: Install ${{ matrix.version }}
+        uses: actions-rs/toolchain@v1
+        with:
+          toolchain: ${{ matrix.version }}-x86_64-unknown-linux-gnu
+          profile: minimal
+          components: rustfmt
+          override: true
+
+      - name: Generate Cargo.lock
+        uses: actions-rs/cargo@v1
+        with:
+          command: generate-lockfile
+
+      - name: Cache cargo registry
+        uses: actions/cache@v1
+        with:
+          path: ~/.cargo/registry
+          key: ${{ matrix.version }}-x86_64-unknown-linux-gnu-cargo-registry-trimmed-${{ hashFiles('**/Cargo.lock') }}
+
+      - name: Cache cargo index
+        uses: actions/cache@v1
+        with:
+          path: ~/.cargo/git
+          key: ${{ matrix.version }}-x86_64-unknown-linux-gnu-cargo-index-trimmed-${{ hashFiles('**/Cargo.lock') }}
+
+      - name: Run tests
+        uses: actions-rs/cargo@v1
+        timeout-minutes: 40
+        with:
+          command: test
+          args: --all --all-features --no-fail-fast -- --nocapture
+
+      - name: Install cargo-cache
+        continue-on-error: true
+        run: |
+          cargo install cargo-cache --no-default-features --features ci-autoclean
+
+      - name: Clear the cargo caches
+        run: |
+          cargo-cache
diff --git a/.github/workflows/osx.yml b/.github/workflows/osx.yml
new file mode 100644 (file)
index 0000000..6d9ffa2
--- /dev/null
@@ -0,0 +1,56 @@
+name: CI (OSX)
+
+on:
+  push:
+  pull_request:
+  schedule: [cron: "40 1 * * *"]
+
+jobs:
+  build_and_test:
+    strategy:
+      fail-fast: false
+      matrix:
+        version:
+          - stable
+          - nightly
+
+    name: ${{ matrix.version }} - x86_64-apple-darwin
+    runs-on: macOS-latest
+
+    steps:
+      - uses: actions/checkout@master
+      - name: Install ${{ matrix.version }}
+        uses: actions-rs/toolchain@v1
+        with:
+          toolchain: ${{ matrix.version }}-x86_64-apple-darwin
+          profile: minimal
+          components: rustfmt
+          override: true
+
+      - name: Generate Cargo.lock
+        uses: actions-rs/cargo@v1
+        with:
+          command: generate-lockfile
+
+      - name: Cache cargo registry
+        uses: actions/cache@v1
+        with:
+          path: ~/.cargo/registry
+          key: ${{ matrix.version }}-x86_64-apple-darwin-cargo-registry-trimmed-${{ hashFiles('**/Cargo.lock') }}
+
+      - name: Cache cargo index
+        uses: actions/cache@v1
+        with:
+          path: ~/.cargo/git
+          key: ${{ matrix.version }}-x86_64-apple-darwin-cargo-index-trimmed-${{ hashFiles('**/Cargo.lock') }}
+
+      - name: Run tests
+        uses: actions-rs/cargo@v1
+        with:
+          command: test
+          args: --all --all-features --no-fail-fast -- --nocapture
+
+      - name: Clear the cargo caches
+        run: |
+          cargo install cargo-cache --no-default-features --features ci-autoclean
+          cargo-cache
diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..a8a1831
--- /dev/null
@@ -0,0 +1,3 @@
+target
+Cargo.lock
+/.idea
diff --git a/.travis.yml b/.travis.yml
new file mode 100644 (file)
index 0000000..89e052b
--- /dev/null
@@ -0,0 +1,28 @@
+language: rust
+sudo: false
+dist: trusty
+
+cache:
+  cargo: false
+
+matrix:
+  include:
+    - rust: stable
+    - rust: beta
+    - rust: 1.40.0
+    - rust: nightly
+
+before_script:
+  - |
+    if [[ "$TRAVIS_RUST_VERSION" != "nightly" ]]; then
+      rustup component add rustfmt
+      rustup component add clippy
+    fi
+
+script:
+  - |
+    if [[ "$TRAVIS_RUST_VERSION" != "nightly" ]]; then
+      cargo fmt -- --check
+      cargo clippy --all-targets --all-features -- -D warnings
+    fi
+  - cargo test --all-features --all -- --nocapture
diff --git a/Cargo.toml b/Cargo.toml
new file mode 100644 (file)
index 0000000..0317866
--- /dev/null
@@ -0,0 +1,47 @@
+# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
+#
+# When uploading crates to the registry Cargo will automatically
+# "normalize" Cargo.toml files for maximal compatibility
+# with all versions of Cargo and also rewrite `path` dependencies
+# to registry (e.g., crates.io) dependencies
+#
+# If you believe there's an error in this file please file an
+# issue against the rust-lang/cargo repository. If you're
+# editing this file be aware that the upstream Cargo.toml
+# will likely look very different (and much more reasonable)
+
+[package]
+edition = "2018"
+name = "yansi-term"
+version = "0.1.2"
+authors = ["ogham@bsago.me", "Ryan Scheel (Havvy) <ryan.havvy@gmail.com>", "Josh Triplett <josh@joshtriplett.org>", "Juan Aguilar Santillana <mhpoin@gmail.com>"]
+description = "Library for ANSI terminal colours and styles (bold, underline)"
+homepage = "https://github.com/botika/yansi-term"
+documentation = "https://docs.rs/yansi-term"
+readme = "README.md"
+license = "MIT"
+repository = "https://github.com/botika/yansi-term"
+[dependencies.serde]
+version = "1.0"
+features = ["derive"]
+optional = true
+[dev-dependencies.doc-comment]
+version = "0.3"
+
+[dev-dependencies.regex]
+version = "1.1"
+
+[dev-dependencies.serde_json]
+version = "1.0"
+
+[features]
+derive_serde_style = ["serde"]
+[target."cfg(target_os=\"windows\")".dependencies.winapi]
+version = "0.3.4"
+features = ["consoleapi", "errhandlingapi", "fileapi", "handleapi", "processenv"]
+[badges.maintenance]
+status = "actively-developed"
+
+[badges.travis-ci]
+branch = "master"
+repository = "botika/yansi-term"
diff --git a/Cargo.toml.orig b/Cargo.toml.orig
new file mode 100644 (file)
index 0000000..3bc457d
--- /dev/null
@@ -0,0 +1,37 @@
+[package]
+name = "yansi-term"
+description = "Library for ANSI terminal colours and styles (bold, underline)"
+authors = [
+    "ogham@bsago.me",
+    "Ryan Scheel (Havvy) <ryan.havvy@gmail.com>",
+    "Josh Triplett <josh@joshtriplett.org>",
+    "Juan Aguilar Santillana <mhpoin@gmail.com>"
+]
+documentation = "https://docs.rs/yansi-term"
+homepage = "https://github.com/botika/yansi-term"
+license = "MIT"
+readme = "README.md"
+version = "0.1.2"
+repository = "https://github.com/botika/yansi-term"
+edition = "2018"
+
+[badges]
+travis-ci = { repository = "botika/yansi-term", branch = "master" }
+maintenance = { status = "actively-developed" }
+
+[features]
+derive_serde_style = ["serde"]
+
+[dependencies.serde]
+version = "1.0"
+features = ["derive"]
+optional = true
+
+[target.'cfg(target_os="windows")'.dependencies.winapi]
+version = "0.3.4"
+features = ["consoleapi", "errhandlingapi", "fileapi", "handleapi", "processenv"]
+
+[dev-dependencies]
+doc-comment = "0.3"
+regex = "1.1"
+serde_json = "1.0"
diff --git a/LICENCE b/LICENCE
new file mode 100644 (file)
index 0000000..3228cc9
--- /dev/null
+++ b/LICENCE
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2014 Benjamin Sago
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/README.md b/README.md
new file mode 100644 (file)
index 0000000..182f0dd
--- /dev/null
+++ b/README.md
@@ -0,0 +1,119 @@
+# yansi-term [![Latest version](https://img.shields.io/crates/v/yansi-term.svg)](https://crates.io/crates/yansi-term) [![Build Status](https://travis-ci.org/botika/yansi-term.svg?branch=master)](https://travis-ci.org/botika/yansi-term)
+> Adapted from [`rust-ansi-term`](https://github.com/ogham/rust-ansi-term)
+
+Refactor for use [`fmt::Display`](https://doc.rust-lang.org/std/fmt/trait.Display.html) and `FnOnce(&mut fmt::Formatter) -> fmt::Result` 
+
+
+This is a library for controlling colours and formatting, such as red bold text or blue underlined text, on ANSI terminals.
+
+### [View the Rustdoc](https://docs.rs/yansi-term)
+
+
+# Installation
+
+This crate works with [Cargo](http://crates.io). Add the following to your `Cargo.toml` dependencies section:
+
+```toml
+[dependencies]
+yansi-term = "0.1"
+```
+
+
+## Basic usage
+
+There are two main types in this crate that you need to be concerned with: `Style`, and `Colour`.
+
+A `Style` holds stylistic information: foreground and background colours, whether the text should be bold, or blinking, or other properties.
+The `Colour` enum represents the available colours.
+
+`Color` is also available as an alias to `Colour`.
+
+To format a string, call the `paint` method on a `Style` or a `Colour`, passing in the string you want to format as the argument.
+For example, here’s how to get some red text:
+
+```rust
+use yansi_term::Colour::Red;
+
+println!("This is in red: {}", Red.paint("a red string"));
+```
+
+**Note for Windows 10 users:** On Windows 10, the application must enable ANSI support first:
+
+```rust,ignore
+let enabled = yansi_term::enable_ansi_support();
+```
+
+## Bold, underline, background, and other styles
+
+For anything more complex than plain foreground colour changes, you need to construct `Style` values themselves, rather than beginning with a `Colour`.
+You can do this by chaining methods based on a new `Style`, created with `Style::new()`.
+Each method creates a new style that has that specific property set.
+For example:
+
+```rust
+use yansi_term::Style;
+
+println!("How about some {} and {}?",
+         Style::new().bold().paint("bold"),
+         Style::new().underline().paint("underline"));
+```
+
+For brevity, these methods have also been implemented for `Colour` values, so you can give your styles a foreground colour without having to begin with an empty `Style` value:
+
+```rust
+use yansi_term::Colour::{Blue, Yellow};
+
+println!("Demonstrating {} and {}!",
+         Blue.bold().paint("blue bold"),
+         Yellow.underline().paint("yellow underline"));
+
+println!("Yellow on blue: {}", Yellow.on(Blue).paint("wow!"));
+```
+
+The complete list of styles you can use are:
+`bold`, `dimmed`, `italic`, `underline`, `blink`, `reverse`, `hidden`, and `on` for background colours.
+
+In some cases, you may find it easier to change the foreground on an existing `Style` rather than starting from the appropriate `Colour`.
+You can do this using the `fg` method:
+
+```rust
+use yansi_term::Style;
+use yansi_term::Colour::{Blue, Cyan, Yellow};
+
+println!("Yellow on blue: {}", Style::new().on(Blue).fg(Yellow).paint("yow!"));
+println!("Also yellow on blue: {}", Cyan.on(Blue).fg(Yellow).paint("zow!"));
+```
+
+You can turn a `Colour` into a `Style` with the `normal` method.
+
+```rust
+use yansi_term::Style;
+use yansi_term::Colour::Red;
+
+Red.normal().paint("yet another red string");
+Style::default().paint("a completely regular string");
+```
+
+
+## Extended colours
+
+You can access the extended range of 256 colours by using the `Colour::Fixed` variant, which takes an argument of the colour number to use.
+This can be included wherever you would use a `Colour`:
+
+```rust
+use yansi_term::Colour::Fixed;
+
+Fixed(134).paint("A sort of light purple");
+Fixed(221).on(Fixed(124)).paint("Mustard in the ketchup");
+```
+
+The first sixteen of these values are the same as the normal and bold standard colour variants.
+There’s nothing stopping you from using these as `Fixed` colours instead, but there’s nothing to be gained by doing so either.
+
+You can also access full 24-bit colour by using the `Colour::RGB` variant, which takes separate `u8` arguments for red, green, and blue:
+
+```rust
+use yansi_term::Colour::RGB;
+
+RGB(70, 130, 180).paint("Steel blue");
+```
diff --git a/examples/256_colours.rs b/examples/256_colours.rs
new file mode 100644 (file)
index 0000000..1a222a7
--- /dev/null
@@ -0,0 +1,76 @@
+extern crate yansi_term;
+use yansi_term::Colour;
+
+// This example prints out the 256 colours.
+// They're arranged like this:
+//
+// - 0 to 8 are the eight standard colours.
+// - 9 to 15 are the eight bold colours.
+// - 16 to 231 are six blocks of six-by-six colour squares.
+// - 232 to 255 are shades of grey.
+
+fn main() {
+    // First two lines
+    for c in 0..8 {
+        glow(c, c != 0);
+        print!(" ");
+    }
+    println!();
+    for c in 8..16 {
+        glow(c, c != 8);
+        print!(" ");
+    }
+    println!("\n");
+
+    // Six lines of the first three squares
+    for row in 0..6 {
+        for square in 0..3 {
+            for column in 0..6 {
+                glow(16 + square * 36 + row * 6 + column, row >= 3);
+                print!(" ");
+            }
+
+            print!("  ");
+        }
+
+        println!();
+    }
+    println!();
+
+    // Six more lines of the other three squares
+    for row in 0..6 {
+        for square in 0..3 {
+            for column in 0..6 {
+                glow(124 + square * 36 + row * 6 + column, row >= 3);
+                print!(" ");
+            }
+
+            print!("  ");
+        }
+
+        println!();
+    }
+    println!();
+
+    // The last greyscale lines
+    for c in 232..=243 {
+        glow(c, false);
+        print!(" ");
+    }
+    println!();
+    for c in 244..=255 {
+        glow(c, true);
+        print!(" ");
+    }
+    println!();
+}
+
+fn glow(c: u8, light_bg: bool) {
+    let base = if light_bg {
+        Colour::Black
+    } else {
+        Colour::White
+    };
+    let style = base.on(Colour::Fixed(c));
+    print!("{}", style.paint_fn(|f| write!(f, " {:3} ", c)));
+}
diff --git a/examples/basic_colours.rs b/examples/basic_colours.rs
new file mode 100644 (file)
index 0000000..666c478
--- /dev/null
@@ -0,0 +1,18 @@
+extern crate yansi_term;
+use yansi_term::{Colour::*, Style};
+
+// This example prints out the 16 basic colours.
+
+fn main() {
+    let normal = Style::default();
+
+    println!("{} {}", normal.paint("Normal"), normal.bold().paint("bold"));
+    println!("{} {}", Black.paint("Black"), Black.bold().paint("bold"));
+    println!("{} {}", Red.paint("Red"), Red.bold().paint("bold"));
+    println!("{} {}", Green.paint("Green"), Green.bold().paint("bold"));
+    println!("{} {}", Yellow.paint("Yellow"), Yellow.bold().paint("bold"));
+    println!("{} {}", Blue.paint("Blue"), Blue.bold().paint("bold"));
+    println!("{} {}", Purple.paint("Purple"), Purple.bold().paint("bold"));
+    println!("{} {}", Cyan.paint("Cyan"), Cyan.bold().paint("bold"));
+    println!("{} {}", White.paint("White"), White.bold().paint("bold"));
+}
diff --git a/examples/overwrite.rs b/examples/overwrite.rs
new file mode 100644 (file)
index 0000000..ed0224f
--- /dev/null
@@ -0,0 +1,18 @@
+extern crate yansi_term;
+use yansi_term::{Colour::*, Style};
+
+// This example prints out the 16 basic colours.
+
+fn main() {
+    println!(
+        "{}",
+        Red.paint_fn(|f| {
+            f.write_str("RED")?;
+            Style::new().bold().write_prefix(f)?;
+            f.write_str("RED_BOLD")?;
+            Style::write_reset(f)?;
+            Black.write_prefix(f)?;
+            f.write_str("BLACK")
+        })
+    );
+}
diff --git a/examples/rgb_colours.rs b/examples/rgb_colours.rs
new file mode 100644 (file)
index 0000000..c28b52e
--- /dev/null
@@ -0,0 +1,23 @@
+extern crate yansi_term;
+use yansi_term::{Colour, Style};
+
+// This example prints out a colour gradient in a grid by calculating each
+// character’s red, green, and blue components, and using 24-bit colour codes
+// to display them.
+
+const WIDTH: i32 = 80;
+const HEIGHT: i32 = 24;
+
+fn main() {
+    for row in 0..HEIGHT {
+        for col in 0..WIDTH {
+            let r = (row * 255 / HEIGHT) as u8;
+            let g = (col * 255 / WIDTH) as u8;
+            let b = 128;
+
+            print!("{}", Style::default().on(Colour::RGB(r, g, b)).paint(" "));
+        }
+
+        println!();
+    }
+}
diff --git a/src/ansi.rs b/src/ansi.rs
new file mode 100644 (file)
index 0000000..159038d
--- /dev/null
@@ -0,0 +1,290 @@
+use std::fmt::{self, Display, Write};
+
+use crate::{Colour, Style};
+
+impl Style {
+    /// Write any bytes that go *before* a piece of text to the given writer.
+    pub fn write_prefix(&self, f: &mut fmt::Formatter) -> Result<bool, fmt::Error> {
+        let mut written_anything = false;
+        macro_rules! write_anything {
+            () => {
+                if written_anything {
+                    f.write_char(';')?;
+                } else {
+                    // Write the codes’ prefix, then write numbers, separated by
+                    // semicolons, for each text style we want to apply.
+                    f.write_str("\x1B[")?;
+                    written_anything = true;
+                }
+            };
+        }
+        macro_rules! write_char {
+            ($cond:ident, $c:expr) => {
+                if self.$cond {
+                    write_anything!();
+                    f.write_char($c)?;
+                }
+            };
+        }
+        macro_rules! write_chars {
+            ($cond:ident => $c:expr) => { write_char!($cond, $c); };
+            ($cond:ident => $c:expr, $($t:tt)+) => {
+                write_char!($cond, $c);
+                write_chars!($($t)+);
+            };
+        }
+
+        write_chars!(
+            is_bold => '1',
+            is_dimmed => '2',
+            is_italic => '3',
+            is_underline => '4',
+            is_blink => '5',
+            is_reverse => '7',
+            is_hidden => '8',
+            is_strikethrough => '9'
+        );
+
+        // The foreground and background colours, if specified, need to be
+        // handled specially because the number codes are more complicated.
+        // (see `write_background_code` and `write_foreground_code`)
+        if let Some(bg) = self.background {
+            write_anything!();
+            bg.write_background_code(f)?;
+        }
+
+        if let Some(fg) = self.foreground {
+            write_anything!();
+            fg.write_foreground_code(f)?;
+        }
+
+        if written_anything {
+            // All the codes end with an `m`, because reasons.
+            f.write_char('m')?;
+        }
+
+        Ok(written_anything)
+    }
+
+    /// Write any bytes that go *after* a piece of text to the given writer.
+    #[inline]
+    pub fn write_reset(f: &mut fmt::Formatter) -> fmt::Result {
+        f.write_str(RESET)
+    }
+}
+
+impl Colour {
+    /// Write any bytes that go *before* a piece of text to the given writer.
+    #[inline]
+    pub fn write_prefix(self, f: &mut fmt::Formatter) -> Result<bool, fmt::Error> {
+        self.normal().write_prefix(f)
+    }
+}
+
+/// The code to send to reset all styles and return to `Style::default()`.
+pub static RESET: &str = "\x1B[0m";
+
+macro_rules! write_color {
+    ($_self:ident, $f:ident =>
+        $black:expr, $red:expr, $green:expr, $yellow:expr, $blue:expr,
+        $purple:expr, $cyan:expr, $white:expr, $fixed:expr, $rgb:expr) => {{
+        use Colour::*;
+        match $_self {
+            Black => $f.write_str($black),
+            Red => $f.write_str($red),
+            Green => $f.write_str($green),
+            Yellow => $f.write_str($yellow),
+            Blue => $f.write_str($blue),
+            Purple => $f.write_str($purple),
+            Cyan => $f.write_str($cyan),
+            White => $f.write_str($white),
+            Fixed(num) => {
+                $f.write_str($fixed)?;
+                num.fmt($f)
+            }
+            RGB(r, g, b) => {
+                $f.write_str($rgb)?;
+                r.fmt($f)?;
+                $f.write_char(';')?;
+                g.fmt($f)?;
+                $f.write_char(';')?;
+                b.fmt($f)
+            }
+        }
+    }};
+}
+
+impl Colour {
+    #[inline]
+    fn write_foreground_code(self, f: &mut fmt::Formatter) -> fmt::Result {
+        write_color!(self, f => "30", "31", "32", "33", "34", "35", "36", "37", "38;5;", "38;2;")
+    }
+
+    #[inline]
+    fn write_background_code(self, f: &mut fmt::Formatter) -> fmt::Result {
+        write_color!(self, f => "40", "41", "42", "43", "44", "45", "46", "47", "48;5;", "48;2;")
+    }
+}
+
+#[cfg(test)]
+mod test {
+    use crate::{Colour::*, Style};
+
+    macro_rules! test {
+        ($name: ident: $style: expr; $input: expr => $result: expr) => {
+            #[test]
+            fn $name() {
+                assert_eq!($style.paint($input).to_string(), $result.to_string());
+            }
+        };
+    }
+
+    test!(plain:                 Style::default();                  "text/plain" => "text/plain");
+    test!(red:                   Red;                               "hi" => "\x1B[31mhi\x1B[0m");
+    test!(black:                 Black.normal();                    "hi" => "\x1B[30mhi\x1B[0m");
+    test!(yellow_bold:           Yellow.bold();                     "hi" => "\x1B[1;33mhi\x1B[0m");
+    test!(yellow_bold_2:         Yellow.normal().bold();            "hi" => "\x1B[1;33mhi\x1B[0m");
+    test!(blue_underline:        Blue.underline();                  "hi" => "\x1B[4;34mhi\x1B[0m");
+    test!(green_bold_ul:         Green.bold().underline();          "hi" => "\x1B[1;4;32mhi\x1B[0m");
+    test!(green_bold_ul_2:       Green.underline().bold();          "hi" => "\x1B[1;4;32mhi\x1B[0m");
+    test!(purple_on_white:       Purple.on(White);                  "hi" => "\x1B[47;35mhi\x1B[0m");
+    test!(purple_on_white_2:     Purple.normal().on(White);         "hi" => "\x1B[47;35mhi\x1B[0m");
+    test!(yellow_on_blue:        Style::new().on(Blue).fg(Yellow);  "hi" => "\x1B[44;33mhi\x1B[0m");
+    test!(yellow_on_blue_2:      Cyan.on(Blue).fg(Yellow);          "hi" => "\x1B[44;33mhi\x1B[0m");
+    test!(cyan_bold_on_white:    Cyan.bold().on(White);             "hi" => "\x1B[1;47;36mhi\x1B[0m");
+    test!(cyan_ul_on_white:      Cyan.underline().on(White);        "hi" => "\x1B[4;47;36mhi\x1B[0m");
+    test!(cyan_bold_ul_on_white: Cyan.bold().underline().on(White); "hi" => "\x1B[1;4;47;36mhi\x1B[0m");
+    test!(cyan_ul_bold_on_white: Cyan.underline().bold().on(White); "hi" => "\x1B[1;4;47;36mhi\x1B[0m");
+    test!(fixed:                 Fixed(100);                        "hi" => "\x1B[38;5;100mhi\x1B[0m");
+    test!(fixed_on_purple:       Fixed(100).on(Purple);             "hi" => "\x1B[45;38;5;100mhi\x1B[0m");
+    test!(fixed_on_fixed:        Fixed(100).on(Fixed(200));         "hi" => "\x1B[48;5;200;38;5;100mhi\x1B[0m");
+    test!(rgb:                   RGB(70,130,180);                   "hi" => "\x1B[38;2;70;130;180mhi\x1B[0m");
+    test!(rgb_on_blue:           RGB(70,130,180).on(Blue);          "hi" => "\x1B[44;38;2;70;130;180mhi\x1B[0m");
+    test!(blue_on_rgb:           Blue.on(RGB(70,130,180));          "hi" => "\x1B[48;2;70;130;180;34mhi\x1B[0m");
+    test!(rgb_on_rgb:            RGB(70,130,180).on(RGB(5,10,15));  "hi" => "\x1B[48;2;5;10;15;38;2;70;130;180mhi\x1B[0m");
+    test!(bold:                  Style::new().bold();               "hi" => "\x1B[1mhi\x1B[0m");
+    test!(underline:             Style::new().underline();          "hi" => "\x1B[4mhi\x1B[0m");
+    test!(bunderline:            Style::new().bold().underline();   "hi" => "\x1B[1;4mhi\x1B[0m");
+    test!(dimmed:                Style::new().dimmed();             "hi" => "\x1B[2mhi\x1B[0m");
+    test!(italic:                Style::new().italic();             "hi" => "\x1B[3mhi\x1B[0m");
+    test!(blink:                 Style::new().blink();              "hi" => "\x1B[5mhi\x1B[0m");
+    test!(reverse:               Style::new().reverse();            "hi" => "\x1B[7mhi\x1B[0m");
+    test!(hidden:                Style::new().hidden();             "hi" => "\x1B[8mhi\x1B[0m");
+    test!(stricken:              Style::new().strikethrough();      "hi" => "\x1B[9mhi\x1B[0m");
+
+    macro_rules! test_fn {
+        ($name:ident: $style:expr; $result:expr) => {
+            #[test]
+            fn $name() {
+                let string = String::from("hi");
+                let string: &str = &string;
+                assert_eq!(
+                    $style.paint_fn(|f| f.write_str(string)).to_string(),
+                    $result.to_string()
+                );
+            }
+        };
+    }
+
+    test_fn!(plain_fn:                 Style::default();                  "hi");
+    test_fn!(red_fn:                   Red;                               "\x1B[31mhi\x1B[0m");
+    test_fn!(black_fn:                 Black.normal();                    "\x1B[30mhi\x1B[0m");
+    test_fn!(yellow_bold_fn:           Yellow.bold();                     "\x1B[1;33mhi\x1B[0m");
+    test_fn!(yellow_bold_2_fn:         Yellow.normal().bold();            "\x1B[1;33mhi\x1B[0m");
+    test_fn!(blue_underline_fn:        Blue.underline();                  "\x1B[4;34mhi\x1B[0m");
+    test_fn!(green_bold_ul_fn:         Green.bold().underline();          "\x1B[1;4;32mhi\x1B[0m");
+    test_fn!(green_bold_ul_2_fn:       Green.underline().bold();          "\x1B[1;4;32mhi\x1B[0m");
+    test_fn!(purple_on_white_fn:       Purple.on(White);                  "\x1B[47;35mhi\x1B[0m");
+    test_fn!(purple_on_white_2_fn:     Purple.normal().on(White);         "\x1B[47;35mhi\x1B[0m");
+    test_fn!(yellow_on_blue_fn:        Style::new().on(Blue).fg(Yellow);  "\x1B[44;33mhi\x1B[0m");
+    test_fn!(yellow_on_blue_2_fn:      Cyan.on(Blue).fg(Yellow);          "\x1B[44;33mhi\x1B[0m");
+    test_fn!(cyan_bold_on_white_fn:    Cyan.bold().on(White);             "\x1B[1;47;36mhi\x1B[0m");
+    test_fn!(cyan_ul_on_white_fn:      Cyan.underline().on(White);        "\x1B[4;47;36mhi\x1B[0m");
+    test_fn!(cyan_bold_ul_on_white_fn: Cyan.bold().underline().on(White); "\x1B[1;4;47;36mhi\x1B[0m");
+    test_fn!(cyan_ul_bold_on_white_fn: Cyan.underline().bold().on(White); "\x1B[1;4;47;36mhi\x1B[0m");
+    test_fn!(fixed_fn:                 Fixed(100);                        "\x1B[38;5;100mhi\x1B[0m");
+    test_fn!(fixed_on_purple_fn:       Fixed(100).on(Purple);             "\x1B[45;38;5;100mhi\x1B[0m");
+    test_fn!(fixed_on_fixed_fn:        Fixed(100).on(Fixed(200));         "\x1B[48;5;200;38;5;100mhi\x1B[0m");
+    test_fn!(rgb_fn:                   RGB(70,130,180);                   "\x1B[38;2;70;130;180mhi\x1B[0m");
+    test_fn!(rgb_on_blue_fn:           RGB(70,130,180).on(Blue);          "\x1B[44;38;2;70;130;180mhi\x1B[0m");
+    test_fn!(blue_on_rgb_fn:           Blue.on(RGB(70,130,180));          "\x1B[48;2;70;130;180;34mhi\x1B[0m");
+    test_fn!(rgb_on_rgb_fn:            RGB(70,130,180).on(RGB(5,10,15));  "\x1B[48;2;5;10;15;38;2;70;130;180mhi\x1B[0m");
+    test_fn!(bold_fn:                  Style::new().bold();               "\x1B[1mhi\x1B[0m");
+    test_fn!(underline_fn:             Style::new().underline();          "\x1B[4mhi\x1B[0m");
+    test_fn!(bunderline_fn:            Style::new().bold().underline();   "\x1B[1;4mhi\x1B[0m");
+    test_fn!(dimmed_fn:                Style::new().dimmed();             "\x1B[2mhi\x1B[0m");
+    test_fn!(italic_fn:                Style::new().italic();             "\x1B[3mhi\x1B[0m");
+    test_fn!(blink_fn:                 Style::new().blink();              "\x1B[5mhi\x1B[0m");
+    test_fn!(reverse_fn:               Style::new().reverse();            "\x1B[7mhi\x1B[0m");
+    test_fn!(hidden_fn:                Style::new().hidden();             "\x1B[8mhi\x1B[0m");
+    test_fn!(stricken_fn:              Style::new().strikethrough();      "\x1B[9mhi\x1B[0m");
+
+    #[test]
+    fn test_move() {
+        let string = String::from("hi");
+        assert_eq!(
+            Style::default()
+                .paint_fn(|f| f.write_str(&string))
+                .to_string(),
+            "hi"
+        );
+    }
+
+    #[test]
+    fn test_ref() {
+        let string = &String::from("hi");
+        assert_eq!(
+            Style::default()
+                .paint_fn(|f| f.write_str(string))
+                .to_string(),
+            "hi"
+        );
+    }
+
+    #[test]
+    fn test_debug() {
+        let a = vec![1, 2, 3];
+        assert_eq!(
+            Style::default()
+                .paint_fn(|f| std::fmt::Debug::fmt(&a, f))
+                .to_string(),
+            "[1, 2, 3]"
+        );
+        assert_eq!(
+            Style::default()
+                .bold()
+                .paint_fn(|f| std::fmt::Debug::fmt(&a, f))
+                .to_string(),
+            "\x1B[1m[1, 2, 3]\x1B[0m"
+        );
+    }
+
+    #[test]
+    fn test_write() {
+        assert_eq!(
+            Style::default()
+                .paint_fn(|f| write!(f, "{:.5}", 1.0))
+                .to_string(),
+            "1.00000"
+        );
+        assert_eq!(
+            Style::default()
+                .bold()
+                .paint_fn(|f| write!(f, "{:.5}", 1.0))
+                .to_string(),
+            "\x1B[1m1.00000\x1B[0m"
+        );
+    }
+
+    /// Can not write the same `impl Display` two or more times
+    /// else return error
+    #[test]
+    fn test_error() {
+        use std::fmt::Write;
+        let a = Style::default().paint("foo");
+        let _ = a.to_string();
+        let mut b = String::new();
+
+        assert!(write!(b, "{}", a).is_err());
+    }
+}
diff --git a/src/display.rs b/src/display.rs
new file mode 100644 (file)
index 0000000..afc4e28
--- /dev/null
@@ -0,0 +1,80 @@
+use std::{cell::Cell, fmt};
+
+use crate::{Colour, Style};
+
+/// An `DisplayANSI` includes a format function and a `Style`
+struct DisplayANSI<F: FnOnce(&mut fmt::Formatter) -> fmt::Result> {
+    style: Style,
+    f: Cell<Option<F>>,
+}
+
+impl<F: FnOnce(&mut fmt::Formatter) -> fmt::Result> fmt::Display for DisplayANSI<F> {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        let written = self.style.write_prefix(f)?;
+        self.f.take().ok_or(fmt::Error).and_then(|c| c(f))?;
+        if written {
+            Style::write_reset(f)?;
+        }
+        Ok(())
+    }
+}
+
+impl Style {
+    /// Paints the given text with this style
+    #[inline]
+    pub fn paint<'a>(self, input: &'a str) -> impl fmt::Display + 'a {
+        DisplayANSI {
+            f: Cell::new(Some(move |f: &mut fmt::Formatter| f.write_str(input))),
+            style: self,
+        }
+    }
+
+    /// Paints the given format function with this style
+    #[inline]
+    pub fn paint_fn<F: FnOnce(&mut fmt::Formatter) -> fmt::Result>(
+        self,
+        f: F,
+    ) -> impl fmt::Display {
+        DisplayANSI {
+            f: Cell::new(Some(f)),
+            style: self,
+        }
+    }
+}
+
+impl Colour {
+    /// Paints the given text with this colour
+    /// This is a short-cut so you don’t have to use `Blue.normal()` just
+    /// to get blue text.
+    ///
+    /// ```
+    /// use yansi_term::Colour::Blue;
+    /// println!("{}", Blue.paint("da ba dee"));
+    /// ```
+    #[inline]
+    pub fn paint<'a>(self, input: &'a str) -> impl fmt::Display + 'a {
+        self.normal().paint(input)
+    }
+
+    /// Paints the given format function with this colour
+    #[inline]
+    pub fn paint_fn<F: FnOnce(&mut fmt::Formatter) -> fmt::Result>(
+        self,
+        f: F,
+    ) -> impl fmt::Display {
+        self.normal().paint_fn(f)
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn no_control_codes_for_plain() {
+        let one = Style::default().paint("one");
+        let two = Style::default().paint("two");
+        let output = format!("{}{}", one, two);
+        assert_eq!(output, "onetwo");
+    }
+}
diff --git a/src/lib.rs b/src/lib.rs
new file mode 100644 (file)
index 0000000..588ce63
--- /dev/null
@@ -0,0 +1,168 @@
+//! > Adapted from [`rust-ansi-term`](https://github.com/ogham/rust-ansi-term)
+//! >
+//! > Refactor for use [`fmt::Display`](https://doc.rust-lang.org/std/fmt/trait.Display.html)
+//! and `FnOnce(&mut fmt::Formatter) -> fmt::Result`
+//! This is a library for controlling colours and formatting, such as
+//! red bold text or blue underlined text, on ANSI terminals.
+//!
+//!
+//! ## Basic usage
+//!
+//! There are three main types in this crate that you need to be
+//! concerned with: [`ANSIString`], [`Style`], and [`Colour`].
+//!
+//! A `Style` holds stylistic information: foreground and background colours,
+//! whether the text should be bold, or blinking, or other properties. The
+//! [`Colour`] enum represents the available colours. And an [`ANSIString`] is a
+//! string paired with a [`Style`].
+//!
+//! [`Color`] is also available as an alias to `Colour`.
+//!
+//! To format a string, call the `paint` method on a `Style` or a `Colour`,
+//! passing in the string you want to format as the argument. For example,
+//! here’s how to get some red text:
+//!
+//! ```
+//! use yansi_term::Colour::Red;
+//!
+//! println!("This is in red: {}", Red.paint("a red string"));
+//! ```
+//!
+//! It’s important to note that the `paint` method does *not* actually return a
+//! string with the ANSI control characters surrounding it. Instead, it returns
+//! that has a [`Display`] implementation that, when formatted, returns the characters.
+//! ```
+//! use yansi_term::Colour::Red;
+//!
+//! let red_string = Red.paint("a red string").to_string();
+//! ```
+//!
+//!
+//! ## Bold, underline, background, and other styles
+//!
+//! For anything more complex than plain foreground colour changes, you need to
+//! construct `Style` values themselves, rather than beginning with a `Colour`.
+//! You can do this by chaining methods based on a new `Style`, created with
+//! [`Style::new()`]. Each method creates a new style that has that specific
+//! property set. For example:
+//!
+//! ```
+//! use yansi_term::Style;
+//!
+//! println!("How about some {} and {}?",
+//!          Style::new().bold().paint("bold"),
+//!          Style::new().underline().paint("underline"));
+//! ```
+//!
+//! For brevity, these methods have also been implemented for `Colour` values,
+//! so you can give your styles a foreground colour without having to begin with
+//! an empty `Style` value:
+//!
+//! ```
+//! use yansi_term::Colour::{Blue, Yellow};
+//!
+//! println!("Demonstrating {} and {}!",
+//!          Blue.bold().paint("blue bold"),
+//!          Yellow.underline().paint("yellow underline"));
+//!
+//! println!("Yellow on blue: {}", Yellow.on(Blue).paint("wow!"));
+//! ```
+//!
+//! The complete list of styles you can use are: [`bold`], [`dimmed`], [`italic`],
+//! [`underline`], [`blink`], [`reverse`], [`hidden`], [`strikethrough`], and [`on`] for
+//! background colours.
+//!
+//! In some cases, you may find it easier to change the foreground on an
+//! existing `Style` rather than starting from the appropriate `Colour`.
+//! You can do this using the [`fg`] method:
+//!
+//! ```
+//! use yansi_term::Style;
+//! use yansi_term::Colour::{Blue, Cyan, Yellow};
+//!
+//! println!("Yellow on blue: {}", Style::new().on(Blue).fg(Yellow).paint("yow!"));
+//! println!("Also yellow on blue: {}", Cyan.on(Blue).fg(Yellow).paint("zow!"));
+//! ```
+//!
+//! You can turn a `Colour` into a `Style` with the [`normal`] method.
+//! This will produce the exact same `ANSIString` as if you just used the
+//! `paint` method on the `Colour` directly, but it’s useful in certain cases:
+//! for example, you may have a method that returns `Styles`, and need to
+//! represent both the “red bold” and “red, but not bold” styles with values of
+//! the same type. The `Style` struct also has a [`Default`] implementation if you
+//! want to have a style with *nothing* set.
+//!
+//! ```
+//! use yansi_term::Style;
+//! use yansi_term::Colour::Red;
+//!
+//! Red.normal().paint("yet another red string");
+//! Style::default().paint("a completely regular string");
+//! ```
+//!
+//!
+//! ## Extended colours
+//!
+//! You can access the extended range of 256 colours by using the `Colour::Fixed`
+//! variant, which takes an argument of the colour number to use. This can be
+//! included wherever you would use a `Colour`:
+//!
+//! ```
+//! use yansi_term::Colour::Fixed;
+//!
+//! Fixed(134).paint("A sort of light purple");
+//! Fixed(221).on(Fixed(124)).paint("Mustard in the ketchup");
+//! ```
+//!
+//! The first sixteen of these values are the same as the normal and bold
+//! standard colour variants. There’s nothing stopping you from using these as
+//! `Fixed` colours instead, but there’s nothing to be gained by doing so
+//! either.
+//!
+//! You can also access full 24-bit colour by using the `Colour::RGB` variant,
+//! which takes separate `u8` arguments for red, green, and blue:
+//!
+//! ```
+//! use yansi_term::Colour::RGB;
+//!
+//! RGB(70, 130, 180).paint("Steel blue");
+//! ```
+
+//! [`Display`]: https://doc.rust-lang.org/std/fmt/trait.Display.html
+//! [`Default`]: https://doc.rust-lang.org/std/default/trait.Default.html
+//! [`Style`]: struct.Style.html
+//! [`Style::new()`]: struct.Style.html#method.new
+//! [`Color`]: enum.Color.html
+//! [`Colour`]: enum.Colour.html
+//!
+//! [`bold`]: struct.Style.html#method.bold
+//! [`dimmed`]: struct.Style.html#method.dimmed
+//! [`italic`]: struct.Style.html#method.italic
+//! [`underline`]: struct.Style.html#method.underline
+//! [`blink`]: struct.Style.html#method.blink
+//! [`reverse`]: struct.Style.html#method.reverse
+//! [`hidden`]: struct.Style.html#method.hidden
+//! [`strikethrough`]: struct.Style.html#method.strikethrough
+//! [`fg`]: struct.Style.html#method.fg
+//! [`on`]: struct.Style.html#method.on
+
+#[cfg(target_os = "windows")]
+extern crate winapi;
+#[cfg(test)]
+#[macro_use]
+extern crate doc_comment;
+
+#[cfg(test)]
+doctest!("../README.md");
+
+mod ansi;
+mod style;
+pub use style::{Colour, Style};
+
+/// Color is a type alias for `Colour`.
+pub use Colour as Color;
+
+mod display;
+
+mod windows;
+pub use windows::*;
diff --git a/src/style.rs b/src/style.rs
new file mode 100644 (file)
index 0000000..aaef739
--- /dev/null
@@ -0,0 +1,541 @@
+/// A style is a collection of properties that can format a string
+/// using ANSI escape codes.
+///
+/// # Examples
+///
+/// ```
+/// use yansi_term::{Style, Colour};
+///
+/// let style = Style::new().bold().on(Colour::Black);
+/// println!("{}", style.paint("Bold on black"));
+/// ```
+#[derive(PartialEq, Clone, Copy, Default, Debug)]
+#[cfg_attr(
+    feature = "derive_serde_style",
+    derive(serde::Deserialize, serde::Serialize)
+)]
+pub struct Style {
+    /// The style's foreground colour, if it has one.
+    pub foreground: Option<Colour>,
+
+    /// The style's background colour, if it has one.
+    pub background: Option<Colour>,
+
+    /// Whether this style is bold.
+    pub is_bold: bool,
+
+    /// Whether this style is dimmed.
+    pub is_dimmed: bool,
+
+    /// Whether this style is italic.
+    pub is_italic: bool,
+
+    /// Whether this style is underlined.
+    pub is_underline: bool,
+
+    /// Whether this style is blinking.
+    pub is_blink: bool,
+
+    /// Whether this style has reverse colours.
+    pub is_reverse: bool,
+
+    /// Whether this style is hidden.
+    pub is_hidden: bool,
+
+    /// Whether this style is struckthrough.
+    pub is_strikethrough: bool,
+}
+
+impl Style {
+    /// Creates a new Style with no properties set.
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// use yansi_term::Style;
+    ///
+    /// let style = Style::new();
+    /// println!("{}", style.paint("hi"));
+    /// ```
+    pub fn new() -> Style {
+        Style::default()
+    }
+
+    /// Returns a `Style` with the bold property set.
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// use yansi_term::Style;
+    ///
+    /// let style = Style::new().bold();
+    /// println!("{}", style.paint("hey"));
+    /// ```
+    pub fn bold(mut self) -> Self {
+        self.is_bold = true;
+        self
+    }
+
+    /// Returns a `Style` with the dimmed property set.
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// use yansi_term::Style;
+    ///
+    /// let style = Style::new().dimmed();
+    /// println!("{}", style.paint("sup"));
+    /// ```
+    pub fn dimmed(mut self) -> Self {
+        self.is_dimmed = true;
+        self
+    }
+
+    /// Returns a `Style` with the italic property set.
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// use yansi_term::Style;
+    ///
+    /// let style = Style::new().italic();
+    /// println!("{}", style.paint("greetings"));
+    /// ```
+    pub fn italic(mut self) -> Self {
+        self.is_italic = true;
+        self
+    }
+
+    /// Returns a `Style` with the underline property set.
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// use yansi_term::Style;
+    ///
+    /// let style = Style::new().underline();
+    /// println!("{}", style.paint("salutations"));
+    /// ```
+    pub fn underline(mut self) -> Self {
+        self.is_underline = true;
+        self
+    }
+
+    /// Returns a `Style` with the blink property set.
+    /// # Examples
+    ///
+    /// ```
+    /// use yansi_term::Style;
+    ///
+    /// let style = Style::new().blink();
+    /// println!("{}", style.paint("wazzup"));
+    /// ```
+    pub fn blink(mut self) -> Self {
+        self.is_blink = true;
+        self
+    }
+
+    /// Returns a `Style` with the reverse property set.
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// use yansi_term::Style;
+    ///
+    /// let style = Style::new().reverse();
+    /// println!("{}", style.paint("aloha"));
+    /// ```
+    pub fn reverse(mut self) -> Self {
+        self.is_reverse = true;
+        self
+    }
+
+    /// Returns a `Style` with the hidden property set.
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// use yansi_term::Style;
+    ///
+    /// let style = Style::new().hidden();
+    /// println!("{}", style.paint("ahoy"));
+    /// ```
+    pub fn hidden(mut self) -> Self {
+        self.is_hidden = true;
+        self
+    }
+
+    /// Returns a `Style` with the strikethrough property set.
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// use yansi_term::Style;
+    ///
+    /// let style = Style::new().strikethrough();
+    /// println!("{}", style.paint("yo"));
+    /// ```
+    pub fn strikethrough(mut self) -> Self {
+        self.is_strikethrough = true;
+        self
+    }
+
+    /// Returns a `Style` with the foreground colour property set.
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// use yansi_term::{Style, Colour};
+    ///
+    /// let style = Style::new().fg(Colour::Yellow);
+    /// println!("{}", style.paint("hi"));
+    /// ```
+    pub fn fg(mut self, foreground: Colour) -> Self {
+        self.foreground = Some(foreground);
+        self
+    }
+
+    /// Returns a `Style` with the background colour property set.
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// use yansi_term::{Style, Colour};
+    ///
+    /// let style = Style::new().on(Colour::Blue);
+    /// println!("{}", style.paint("eyyyy"));
+    /// ```
+    pub fn on(mut self, background: Colour) -> Self {
+        self.background = Some(background);
+        self
+    }
+
+    /// Return true if this `Style` has no actual styles, and can be written
+    /// without any control characters.
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// use yansi_term::Style;
+    ///
+    /// assert_eq!(true,  Style::default().is_plain());
+    /// assert_eq!(false, Style::default().bold().is_plain());
+    /// ```
+    pub fn is_plain(&self) -> bool {
+        *self == Style::default()
+    }
+}
+
+/// A colour is one specific type of ANSI escape code, and can refer
+/// to either the foreground or background colour.
+///
+/// These use the standard numeric sequences.
+/// See <http://invisible-island.net/xterm/ctlseqs/ctlseqs.html>
+#[derive(PartialEq, Clone, Copy, Debug)]
+#[cfg_attr(
+    feature = "derive_serde_style",
+    derive(serde::Deserialize, serde::Serialize)
+)]
+pub enum Colour {
+    /// Colour #0 (foreground code `30`, background code `40`).
+    ///
+    /// This is not necessarily the background colour, and using it as one may
+    /// render the text hard to read on terminals with dark backgrounds.
+    Black,
+
+    /// Colour #1 (foreground code `31`, background code `41`).
+    Red,
+
+    /// Colour #2 (foreground code `32`, background code `42`).
+    Green,
+
+    /// Colour #3 (foreground code `33`, background code `43`).
+    Yellow,
+
+    /// Colour #4 (foreground code `34`, background code `44`).
+    Blue,
+
+    /// Colour #5 (foreground code `35`, background code `45`).
+    Purple,
+
+    /// Colour #6 (foreground code `36`, background code `46`).
+    Cyan,
+
+    /// Colour #7 (foreground code `37`, background code `47`).
+    ///
+    /// As above, this is not necessarily the foreground colour, and may be
+    /// hard to read on terminals with light backgrounds.
+    White,
+
+    /// A colour number from 0 to 255, for use in 256-colour terminal
+    /// environments.
+    ///
+    /// - Colours 0 to 7 are the `Black` to `White` variants respectively.
+    ///   These colours can usually be changed in the terminal emulator.
+    /// - Colours 8 to 15 are brighter versions of the eight colours above.
+    ///   These can also usually be changed in the terminal emulator, or it
+    ///   could be configured to use the original colours and show the text in
+    ///   bold instead. It varies depending on the program.
+    /// - Colours 16 to 231 contain several palettes of bright colours,
+    ///   arranged in six squares measuring six by six each.
+    /// - Colours 232 to 255 are shades of grey from black to white.
+    ///
+    /// It might make more sense to look at a [colour chart][cc].
+    ///
+    /// [cc]: https://upload.wikimedia.org/wikipedia/commons/1/15/Xterm_256color_chart.svg
+    Fixed(u8),
+
+    /// A 24-bit RGB color, as specified by ISO-8613-3.
+    RGB(u8, u8, u8),
+}
+
+impl Colour {
+    /// Returns a `Style` with the foreground colour set to this colour.
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// use yansi_term::Colour;
+    ///
+    /// let style = Colour::Red.normal();
+    /// println!("{}", style.paint("hi"));
+    /// ```
+    pub fn normal(self) -> Style {
+        Style {
+            foreground: Some(self),
+            ..Style::default()
+        }
+    }
+
+    /// Returns a `Style` with the foreground colour set to this colour and the
+    /// bold property set.
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// use yansi_term::Colour;
+    ///
+    /// let style = Colour::Green.bold();
+    /// println!("{}", style.paint("hey"));
+    /// ```
+    pub fn bold(self) -> Style {
+        Style {
+            foreground: Some(self),
+            is_bold: true,
+            ..Style::default()
+        }
+    }
+
+    /// Returns a `Style` with the foreground colour set to this colour and the
+    /// dimmed property set.
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// use yansi_term::Colour;
+    ///
+    /// let style = Colour::Yellow.dimmed();
+    /// println!("{}", style.paint("sup"));
+    /// ```
+    pub fn dimmed(self) -> Style {
+        Style {
+            foreground: Some(self),
+            is_dimmed: true,
+            ..Style::default()
+        }
+    }
+
+    /// Returns a `Style` with the foreground colour set to this colour and the
+    /// italic property set.
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// use yansi_term::Colour;
+    ///
+    /// let style = Colour::Blue.italic();
+    /// println!("{}", style.paint("greetings"));
+    /// ```
+    pub fn italic(self) -> Style {
+        Style {
+            foreground: Some(self),
+            is_italic: true,
+            ..Style::default()
+        }
+    }
+
+    /// Returns a `Style` with the foreground colour set to this colour and the
+    /// underline property set.
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// use yansi_term::Colour;
+    ///
+    /// let style = Colour::Purple.underline();
+    /// println!("{}", style.paint("salutations"));
+    /// ```
+    pub fn underline(self) -> Style {
+        Style {
+            foreground: Some(self),
+            is_underline: true,
+            ..Style::default()
+        }
+    }
+
+    /// Returns a `Style` with the foreground colour set to this colour and the
+    /// blink property set.
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// use yansi_term::Colour;
+    ///
+    /// let style = Colour::Cyan.blink();
+    /// println!("{}", style.paint("wazzup"));
+    /// ```
+    pub fn blink(self) -> Style {
+        Style {
+            foreground: Some(self),
+            is_blink: true,
+            ..Style::default()
+        }
+    }
+
+    /// Returns a `Style` with the foreground colour set to this colour and the
+    /// reverse property set.
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// use yansi_term::Colour;
+    ///
+    /// let style = Colour::Black.reverse();
+    /// println!("{}", style.paint("aloha"));
+    /// ```
+    pub fn reverse(self) -> Style {
+        Style {
+            foreground: Some(self),
+            is_reverse: true,
+            ..Style::default()
+        }
+    }
+
+    /// Returns a `Style` with the foreground colour set to this colour and the
+    /// hidden property set.
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// use yansi_term::Colour;
+    ///
+    /// let style = Colour::White.hidden();
+    /// println!("{}", style.paint("ahoy"));
+    /// ```
+    pub fn hidden(self) -> Style {
+        Style {
+            foreground: Some(self),
+            is_hidden: true,
+            ..Style::default()
+        }
+    }
+
+    /// Returns a `Style` with the foreground colour set to this colour and the
+    /// strikethrough property set.
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// use yansi_term::Colour;
+    ///
+    /// let style = Colour::Fixed(244).strikethrough();
+    /// println!("{}", style.paint("yo"));
+    /// ```
+    pub fn strikethrough(self) -> Style {
+        Style {
+            foreground: Some(self),
+            is_strikethrough: true,
+            ..Style::default()
+        }
+    }
+
+    /// Returns a `Style` with the foreground colour set to this colour and the
+    /// background colour property set to the given colour.
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// use yansi_term::Colour;
+    ///
+    /// let style = Colour::RGB(31, 31, 31).on(Colour::White);
+    /// println!("{}", style.paint("eyyyy"));
+    /// ```
+    pub fn on(self, background: Colour) -> Style {
+        Style {
+            foreground: Some(self),
+            background: Some(background),
+            ..Style::default()
+        }
+    }
+}
+
+impl From<Colour> for Style {
+    /// You can turn a `Colour` into a `Style` with the foreground colour set
+    /// with the `From` trait.
+    ///
+    /// ```
+    /// use yansi_term::{Style, Colour};
+    /// let green_foreground = Style::default().fg(Colour::Green);
+    /// assert_eq!(green_foreground, Colour::Green.normal());
+    /// assert_eq!(green_foreground, Colour::Green.into());
+    /// assert_eq!(green_foreground, Style::from(Colour::Green));
+    /// ```
+    fn from(colour: Colour) -> Style {
+        colour.normal()
+    }
+}
+
+#[cfg(test)]
+#[cfg(feature = "derive_serde_style")]
+mod serde_json_tests {
+    use super::{Colour, Style};
+
+    #[test]
+    fn colour_serialization() {
+        let colours = &[
+            Colour::Red,
+            Colour::Blue,
+            Colour::RGB(123, 123, 123),
+            Colour::Fixed(255),
+        ];
+
+        assert_eq!(
+            serde_json::to_string(&colours).unwrap(),
+            String::from("[\"Red\",\"Blue\",{\"RGB\":[123,123,123]},{\"Fixed\":255}]")
+        );
+    }
+
+    #[test]
+    fn colour_deserialization() {
+        let colours = &[
+            Colour::Red,
+            Colour::Blue,
+            Colour::RGB(123, 123, 123),
+            Colour::Fixed(255),
+        ];
+
+        for colour in colours {
+            let serialized = serde_json::to_string(&colour).unwrap();
+            let deserialized: Colour = serde_json::from_str(&serialized).unwrap();
+
+            assert_eq!(colour, &deserialized);
+        }
+    }
+
+    #[test]
+    fn style_serialization() {
+        let style = Style::default();
+
+        assert_eq!(serde_json::to_string(&style).unwrap(), "{\"foreground\":null,\"background\":null,\"is_bold\":false,\"is_dimmed\":false,\"is_italic\":false,\"is_underline\":false,\"is_blink\":false,\"is_reverse\":false,\"is_hidden\":false,\"is_strikethrough\":false}".to_string());
+    }
+}
diff --git a/src/windows.rs b/src/windows.rs
new file mode 100644 (file)
index 0000000..3df9ff4
--- /dev/null
@@ -0,0 +1,61 @@
+/// Enables ANSI code support on Windows 10.
+///
+/// This uses Windows API calls to alter the properties of the console that
+/// the program is running in.
+///
+/// https://msdn.microsoft.com/en-us/library/windows/desktop/mt638032(v=vs.85).aspx
+///
+/// Returns a `Result` with the Windows error code if unsuccessful.
+#[cfg(windows)]
+pub fn enable_ansi_support() -> Result<(), u32> {
+    // ref: https://docs.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences#EXAMPLE_OF_ENABLING_VIRTUAL_TERMINAL_PROCESSING @@ https://archive.is/L7wRJ#76%
+
+    use std::{ffi::OsStr, iter::once, os::windows::ffi::OsStrExt, ptr::null_mut};
+    use winapi::um::{
+        consoleapi::{GetConsoleMode, SetConsoleMode},
+        errhandlingapi::GetLastError,
+        fileapi::{CreateFileW, OPEN_EXISTING},
+        handleapi::INVALID_HANDLE_VALUE,
+        winnt::{FILE_SHARE_WRITE, GENERIC_READ, GENERIC_WRITE},
+    };
+
+    const ENABLE_VIRTUAL_TERMINAL_PROCESSING: u32 = 0x0004;
+
+    unsafe {
+        // ref: https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilew
+        // Using `CreateFileW("CONOUT$", ...)` to retrieve the console handle works correctly even if STDOUT and/or STDERR are redirected
+        let console_out_name: Vec<u16> =
+            OsStr::new("CONOUT$").encode_wide().chain(once(0)).collect();
+        let console_handle = CreateFileW(
+            console_out_name.as_ptr(),
+            GENERIC_READ | GENERIC_WRITE,
+            FILE_SHARE_WRITE,
+            null_mut(),
+            OPEN_EXISTING,
+            0,
+            null_mut(),
+        );
+        if console_handle == INVALID_HANDLE_VALUE {
+            return Err(GetLastError());
+        }
+
+        // ref: https://docs.microsoft.com/en-us/windows/console/getconsolemode
+        let mut console_mode: u32 = 0;
+        if 0 == GetConsoleMode(console_handle, &mut console_mode) {
+            return Err(GetLastError());
+        }
+
+        // VT processing not already enabled?
+        if console_mode & ENABLE_VIRTUAL_TERMINAL_PROCESSING == 0 {
+            // https://docs.microsoft.com/en-us/windows/console/setconsolemode
+            if 0 == SetConsoleMode(
+                console_handle,
+                console_mode | ENABLE_VIRTUAL_TERMINAL_PROCESSING,
+            ) {
+                return Err(GetLastError());
+            }
+        }
+    }
+
+    return Ok(());
+}