ansiterm-0.12.2/.appveyor.yml000064400000000000000000000166431046102023000142100ustar 00000000000000# spell-checker:words POSIX repo SDK SDKs toolchain toolchains # spell-checker:ignore ARCH ABI BACKTRACE BINDIR cl COMNTOOLS dllcrt findstr maint MINGW MINGWDIR mkdir MSVC MSYS rustc rustlib rustup targetting USERPROFILE vcvarsall version: "{build} ~ {branch}" branches: except: - gh-pages os: Visual Studio 2015 artifacts: # - path: target\%TARGET%\debug\....exe # name: ....exe matrix: allow_failures: - CHANNEL: nightly # - ABI: gnu environment: global: FEATURES: BUILD_OPTIONS: TEST_OPTIONS: "--no-fail-fast" matrix: # minimum version - CHANNEL: 1.28.0 ARCH: i686 ABI: msvc # "msvc" ABI - CHANNEL: stable ARCH: i686 ABI: msvc - CHANNEL: stable ARCH: x86_64 ABI: msvc # - CHANNEL: beta # ARCH: i686 # ABI: msvc # - CHANNEL: beta # ARCH: x86_64 # ABI: msvc - CHANNEL: nightly ARCH: i686 ABI: msvc - CHANNEL: nightly ARCH: x86_64 ABI: msvc # "gnu" ABI - CHANNEL: stable ARCH: i686 ABI: gnu - CHANNEL: stable ARCH: x86_64 ABI: gnu # - CHANNEL: beta # ARCH: i686 # ABI: gnu # - CHANNEL: beta # ARCH: x86_64 # ABI: gnu # - CHANNEL: nightly # ARCH: i686 # ABI: gnu # - CHANNEL: nightly # ARCH: x86_64 # ABI: gnu # # * specific gnu compilers # - CHANNEL: stable # ARCH: i686 # ABI: gnu # MINGW_URL: https://sourceforge.net/projects/mingw-w64/files/Toolchains%20targetting%20Win32/Personal%20Builds/mingw-builds/4.9.2/threads-win32/dwarf/i686-4.9.2-release-win32-dwarf-rt_v4-rev4.7z/download # MINGW_ARCHIVE: i686-4.9.2-release-win32-dwarf-rt_v4-rev4.7z # - CHANNEL: stable # ARCH: x86_64 # ABI: gnu # MINGW_URL: https://sourceforge.net/projects/mingw-w64/files/Toolchains%20targetting%20Win64/Personal%20Builds/mingw-builds/7.3.0/threads-posix/seh/x86_64-7.3.0-release-posix-seh-rt_v5-rev0.7z/download#mingw-w64-x86_64-7.3.0-posix-seh.7z install: # force branch checkout (if knowable), then reset to the specific commit ## (can be needed for accurate code coverage info) # * this allows later apps to see the branch name using standard `git branch` operations, yet always builds the correct specific commit # * ref: [`@`](https://archive.is/RVpnF) - if DEFINED APPVEYOR_REPO_BRANCH if /I "%APPVEYOR_REPO_SCM%"=="git" ( git checkout "%APPVEYOR_REPO_BRANCH%" 2>NUL & git reset --hard "%APPVEYOR_REPO_COMMIT%" ) # ensure CWD is project main directory - cd "%APPVEYOR_BUILD_FOLDER%" # create a working area - ps: if ( ! $env:CI_TEMP_DIR ) { $env:CI_TEMP_DIR = "${env:TEMP}\${env:APPVEYOR_JOB_ID}" ; mkdir -force $env:CI_TEMP_DIR | out-null } # define the TARGET host - set "TARGET=%ARCH%-pc-windows-%ABI%" # show relevant environment settings - ps: ('CHANNEL', 'ARCH', 'ABI', 'FEATURES', 'BUILD_OPTIONS', 'TEST_OPTIONS', 'TARGET') |% { write-host -f yellow "$_=$(get-content -ea silent env:/$_)" } # rust installation # * install `rust` via `rustup` - appveyor DownloadFile "https://win.rustup.rs/" -FileName "%CI_TEMP_DIR%\rustup-init.exe" - call "%CI_TEMP_DIR%\rustup-init.exe" -y --default-toolchain %CHANNEL% --default-host %TARGET% --no-modify-path >NUL - set "PATH=%PATH%;%USERPROFILE%\.cargo\bin" - ps: $env:TOOLCHAIN = $("$(rustup show active-toolchain)" -split '\s+')[0] # * set RUST_BACKTRACE for enhanced error messages - set RUST_BACKTRACE=1 # * show rust versions - rustc -vV - cargo -vV # "gnu" ABI setup # * use the system MinGW/MSYS if we can - if /i "%ABI%"=="gnu" set MSYS_BINDIR=C:\msys64\usr\bin - if /i "%ABI%"=="gnu" if /i "%ARCH%"=="i686" set "MSYS_BITS=32" - if /i "%ABI%"=="gnu" if /i "%ARCH%"=="x86_64" set "MSYS_BITS=64" - if defined MSYS_BITS set "MSYS_MINGWDIR=C:\msys64\mingw%MSYS_BITS%" - if defined MSYS_MINGWDIR set "MSYS_BINDIR=C:\msys64\usr\bin" ## * workaround for rust-lang/rust#47048 / rust-lang/rust#53454 ## !maint: remove when resolved - if /i "%ABI%"=="gnu" if /i "%ARCH%"=="i686" if not DEFINED MINGW_URL set "MINGW_URL=https://sourceforge.net/projects/mingw-w64/files/Toolchains targetting Win32/Personal Builds/mingw-builds/8.1.0/threads-posix/dwarf/i686-8.1.0-release-posix-dwarf-rt_v6-rev0.7z" - if /i "%ABI%"=="gnu" if /i "%ARCH%"=="x86_64" if not DEFINED MINGW_URL set "MINGW_URL=https://sourceforge.net/projects/mingw-w64/files/Toolchains targetting Win64/Personal Builds/mingw-builds/8.1.0/threads-posix/seh/x86_64-8.1.0-release-posix-seh-rt_v6-rev0.7z" ## (end workaround) # * specific MinGW, if specified - ps: if ( ! $env:MINGW_ARCHIVE -and $env:MINGW_URL ) { $env:MINGW_ARCHIVE = $($([URI]$env:MINGW_URL).fragment).TrimStart('#') } - ps: if ( ! $env:MINGW_ARCHIVE -and $env:MINGW_URL ) { $env:MINGW_ARCHIVE = $([URI]$env:MINGW_URL).segments[-1] } - if defined MINGW_ARCHIVE curl --insecure -fsSL "%MINGW_URL%" -o "%CI_TEMP_DIR%\%MINGW_ARCHIVE%" - if defined MINGW_ARCHIVE mkdir "%CI_TEMP_DIR%\MinGW" >NUL - if defined MINGW_ARCHIVE 7z x -y "%CI_TEMP_DIR%\%MINGW_ARCHIVE%" -o"%CI_TEMP_DIR%\MinGW" >NUL - if defined MINGW_ARCHIVE set "MSYS_MINGWDIR=%CI_TEMP_DIR%\MinGW\mingw%MSYS_BITS%" - if defined MINGW_ARCHIVE set "MSYS_BINDIR=%MSYS_MINGWDIR%\bin" # * MinGW/MSYS PATH setup - if defined MSYS_MINGWDIR set PATH=%MSYS_MINGWDIR%\%ARCH%-w64-mingw32\bin;%MSYS_BINDIR%;%PATH% ## * workaround for rust-lang/rust#47048 / rust-lang/rust#53454 ## !maint: remove when resolved # ** ref: , # ** egs: , - if /i "%ABI%"=="gnu" rustup install %CHANNEL%-%ARCH%-pc-windows-msvc - if /i "%ABI%"=="gnu" rustup default %CHANNEL%-%ARCH%-pc-windows-msvc - if /i "%ABI%"=="gnu" rustup target add %TARGET% - ps: $env:TOOLCHAIN = $("$(rustup show active-toolchain)" -split '\s+')[0] - if /i "%ABI%"=="gnu" rustup show - if /i "%ABI%"=="gnu" rustc -vV # ** copy libs from gcc toolchain to rust toolchain (more specifically, "crt2.o" and "dllcrt2.o" are needed) - if defined MSYS_MINGWDIR copy /y "%MSYS_MINGWDIR%\%ARCH%-w64-mingw32\lib\*.o" "%USERPROFILE%\.rustup\toolchains\%TOOLCHAIN%\lib\rustlib\%TARGET%\lib" >NUL ## (end workaround) # * show `gcc` info - if /i "%ABI%"=="gnu" ( where gcc && gcc --version ) # "msvc" ABI setup - if /i "%ABI%" == "msvc" if /i "%ARCH%" == "i686" call "%VS140COMNTOOLS%\..\..\VC\vcvarsall.bat" - if /i "%ABI%" == "msvc" if /i "%ARCH%" == "x86_64" call "C:\Program Files\Microsoft SDKs\Windows\v7.1\Bin\SetEnv.cmd" /x64 - if /i "%ABI%" == "msvc" if /i "%ARCH%" == "x86_64" call "%VS140COMNTOOLS%\..\..\VC\vcvarsall.bat" x86_amd64 # * show `cl` info - if /i "%ABI%"=="msvc" ( where cl && cl 2>&1 | findstr /i /c:"version" ) # finalize options - ps: if ("$env:FEATURES".length) { $env:BUILD_OPTIONS = $(($env:BUILD_OPTIONS, "--features `"${env:FEATURES}`"")|?{$_}) -join ' ' } - ps: if ("$env:FEATURES".length) { $env:TEST_OPTIONS = $(($env:TEST_OPTIONS, "--features `"${env:FEATURES}`"")|?{$_}) -join ' ' } build_script: - ps: $env:BUILD_CMD = $(("cargo +${env:TOOLCHAIN} build --target=${env:TARGET}", $env:BUILD_OPTIONS)|?{$_}) -join ' ' - echo [ `%BUILD_CMD%` ] & %BUILD_CMD% test_script: - ps: $env:TEST_CMD = $(("cargo +${env:TOOLCHAIN} test --target=${env:TARGET}", $env:TEST_OPTIONS)|?{$_}) -join ' ' - echo [ `%TEST_CMD%` ] & %TEST_CMD% ansiterm-0.12.2/.cargo_vcs_info.json0000644000000001360000000000100127400ustar { "git": { "sha1": "b35c16ee998c18f52ffd1ef7da5a031090175017" }, "path_in_vcs": "" }ansiterm-0.12.2/.github/dependabot.yml000064400000000000000000000012541046102023000157220ustar 00000000000000# To get started with Dependabot version updates, you'll need to specify which # package ecosystems to update and where the package manifests are located. # Please see the documentation for all configuration options: # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates version: 2 updates: - package-ecosystem: "cargo" # See documentation for possible values directory: "/" # Location of package manifests schedule: interval: "weekly" - package-ecosystem: "github-actions" # Workflow files stored in the # default location of `.github/workflows` directory: "/" schedule: interval: "weekly" ansiterm-0.12.2/.github/workflows/audit.yml000064400000000000000000000005371046102023000167630ustar 00000000000000name: Security audit on: schedule: - cron: '0 0 * * *' push: paths: - '**/Cargo.toml' - '**/Cargo.lock' jobs: security_audit: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - uses: taiki-e/install-action@cargo-deny - name: Scan for vulnerabilities run: cargo deny check advisoriesansiterm-0.12.2/.github/workflows/general.yml000064400000000000000000000022431046102023000172660ustar 00000000000000name: Rust on: [push, pull_request] env: CARGO_TERM_COLOR: always jobs: test: name: Test runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - uses: dtolnay/rust-toolchain@stable - uses: Swatinem/rust-cache@v2 - name: Run tests run: cargo test fmt: name: Rustfmt runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - uses: dtolnay/rust-toolchain@stable with: components: rustfmt - name: Enforce formatting run: cargo fmt --check clippy: name: Clippy runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - uses: dtolnay/rust-toolchain@stable with: components: clippy - uses: Swatinem/rust-cache@v2 - name: Linting run: cargo clippy -- -D warnings coverage: name: Code coverage runs-on: ubuntu-latest container: image: xd009642/tarpaulin options: --security-opt seccomp=unconfined steps: - name: Checkout repository uses: actions/checkout@v3 - name: Generate code coverage run: | cargo tarpaulin --verbose --workspace ansiterm-0.12.2/.gitignore000064400000000000000000000000211046102023000135110ustar 00000000000000target Cargo.lockansiterm-0.12.2/.rustfmt.toml000064400000000000000000000000531046102023000142050ustar 00000000000000disable_all_formatting = true # For now... ansiterm-0.12.2/.travis.yml000064400000000000000000000012351046102023000136420ustar 00000000000000language: rust addons: apt: packages: - libcurl4-openssl-dev - libelf-dev - libdw-dev rust: - nightly - beta - stable # load travis-cargo before_script: - | pip install 'travis-cargo<0.2' --user && export PATH=$HOME/.local/bin:$PATH script: - | travis-cargo build travis-cargo build --examples travis-cargo test travis-cargo --only stable doc after_success: # measure code coverage and upload to coveralls.io - travis-cargo coveralls --no-sudo env: global: # override the default `--features unstable` used for the nightly branch (optional) - TRAVIS_CARGO_NIGHTLY_FEATURE=nightly ansiterm-0.12.2/Cargo.lock0000644000000106060000000000100107160ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 3 [[package]] name = "aho-corasick" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6748e8def348ed4d14996fa801f4122cd763fff530258cdc03f64b25f89d3a5a" dependencies = [ "memchr", ] [[package]] name = "ansi_colours" version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a1558bd2075d341b9ca698ec8eb6fcc55a746b1fc4255585aad5b141d918a80" [[package]] name = "ansiterm" version = "0.12.2" dependencies = [ "ansi_colours", "doc-comment", "regex", "serde", "serde_json", "winapi", ] [[package]] name = "doc-comment" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" [[package]] name = "itoa" version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" [[package]] name = "memchr" version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "76fc44e2588d5b436dbc3c6cf62aef290f90dab6235744a93dfe1cc18f451e2c" [[package]] name = "proc-macro2" version = "1.0.66" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9" dependencies = [ "unicode-ident", ] [[package]] name = "quote" version = "1.0.33" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" dependencies = [ "proc-macro2", ] [[package]] name = "regex" version = "1.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "12de2eff854e5fa4b1295edd650e227e9d8fb0c9e90b12e7f36d6a6811791a29" dependencies = [ "aho-corasick", "memchr", "regex-automata", "regex-syntax", ] [[package]] name = "regex-automata" version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49530408a136e16e5b486e883fbb6ba058e8e4e8ae6621a77b048b314336e629" dependencies = [ "aho-corasick", "memchr", "regex-syntax", ] [[package]] name = "regex-syntax" version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da" [[package]] name = "ryu" version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" [[package]] name = "serde" version = "1.0.188" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf9e0fcba69a370eed61bcf2b728575f726b50b55cba78064753d708ddc7549e" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" version = "1.0.188" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "serde_json" version = "1.0.105" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "693151e1ac27563d6dbcec9dee9fbd5da8539b20fa14ad3752b2e6d363ace360" dependencies = [ "itoa", "ryu", "serde", ] [[package]] name = "syn" version = "2.0.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c324c494eba9d92503e6f1ef2e6df781e78f6a7705a0202d9801b198807d518a" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "unicode-ident" version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" [[package]] name = "winapi" version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" dependencies = [ "winapi-i686-pc-windows-gnu", "winapi-x86_64-pc-windows-gnu", ] [[package]] name = "winapi-i686-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" ansiterm-0.12.2/Cargo.toml0000644000000031050000000000100107350ustar # 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 are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2021" name = "ansiterm" version = "0.12.2" authors = [ "Preston Thorpe ", "Sandro-Alessio Gierens ", "Benjamin Sago ", "Ryan Scheel (Havvy) ", "Josh Triplett ", ] description = "Library for ANSI terminal colours and styles (bold, underline)" homepage = "https://github.com/rustadopt/ansiterm-rs" documentation = "https://docs.rs/ansiterm" readme = "README.md" license = "MIT" repository = "https://github.com/rustadopt/ansiterm-rs" [lib] name = "ansiterm" [dependencies.ansi_colours] version = "1.1.1" optional = true default-features = false [dependencies.serde] version = "1.0.90" features = ["derive"] optional = true [dev-dependencies.doc-comment] version = "0.3" [dev-dependencies.regex] version = "1.1.9" [dev-dependencies.serde_json] version = "1.0.39" [features] derive_serde_style = ["serde"] [target."cfg(target_os=\"windows\")".dependencies.winapi] version = "0.3.4" features = [ "consoleapi", "errhandlingapi", "fileapi", "handleapi", "processenv", ] ansiterm-0.12.2/Cargo.toml.orig000064400000000000000000000020701046102023000144160ustar 00000000000000[package] name = "ansiterm" description = "Library for ANSI terminal colours and styles (bold, underline)" edition = "2021" authors = [ "Preston Thorpe ", "Sandro-Alessio Gierens ", "Benjamin Sago ", "Ryan Scheel (Havvy) ", "Josh Triplett " ] documentation = "https://docs.rs/ansiterm" homepage = "https://github.com/rustadopt/ansiterm-rs" license = "MIT" readme = "README.md" version = "0.12.2" repository = "https://github.com/rustadopt/ansiterm-rs" [lib] name = "ansiterm" [features] derive_serde_style = ["serde"] [dependencies.serde] version = "1.0.90" features = ["derive"] optional = true [dependencies.ansi_colours] version = "1.1.1" default-features = false 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.9" [dev-dependencies.serde_json] version = "1.0.39" ansiterm-0.12.2/LICENCE000064400000000000000000000020701046102023000125140ustar 00000000000000The 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. ansiterm-0.12.2/README.md000064400000000000000000000167711046102023000130230ustar 00000000000000# ansiterm-rs Continuation of the unmaintained [ansi-term](https://github.com/ogham/rust-ansi-term), a library for controlling colours and formatting, such as red bold text or blue underlined text, on ANSI terminals. Big shout-out to its creator [Benjamin Sago](https://github.com/ogham). # Installation Disclaimer: This crate is still in development, and is not yet available on crates.io #### We promise to keep the API stable, and not break anything that is already working in the original crate. ## 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: ```rust use ansiterm::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 an `ANSIString` value that has a `Display` implementation that, when formatted, returns the characters. This allows strings to be printed with a minimum of `String` allocations being performed behind the scenes. If you *do* want to get at the escape codes, then you can convert the `ANSIString` to a string as you would any other `Display` value: ```rust use ansiterm::Colour::Red; let red_string = Red.paint("a red string").to_string(); ``` **Note for Windows 10 users:** On Windows 10, the application must enable ANSI support first: ```rust,ignore let enabled = ansiterm::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 ansiterm::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 ansiterm::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 ansiterm::Style; use ansiterm::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. ```rust use ansiterm::Style; use ansiterm::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 ansiterm::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 ansiterm::Colour::RGB; RGB(70, 130, 180).paint("Steel blue"); ``` ## Combining successive coloured strings The benefit of writing ANSI escape codes to the terminal is that they *stack*: you do not need to end every coloured string with a reset code if the text that follows it is of a similar style. For example, if you want to have some blue text followed by some blue bold text, it’s possible to send the ANSI code for blue, followed by the ANSI code for bold, and finishing with a reset code without having to have an extra one between the two strings. This crate can optimise the ANSI codes that get printed in situations like this, making life easier for your terminal renderer. The `ANSIStrings` struct takes a slice of several `ANSIString` values, and will iterate over each of them, printing only the codes for the styles that need to be updated as part of its formatting routine. The following code snippet uses this to enclose a binary number displayed in red bold text inside some red, but not bold, brackets: ```rust use ansiterm::Colour::Red; use ansiterm::{ANSIString, ANSIStrings}; let some_value = format!("{:b}", 42); let strings: &[ANSIString<'static>] = &[ Red.paint("["), Red.bold().paint(some_value), Red.paint("]"), ]; println!("Value: {}", ANSIStrings(strings)); ``` There are several things to note here. Firstly, the `paint` method can take *either* an owned `String` or a borrowed `&str`. Internally, an `ANSIString` holds a copy-on-write (`Cow`) string value to deal with both owned and borrowed strings at the same time. This is used here to display a `String`, the result of the `format!` call, using the same mechanism as some statically-available `&str` slices. Secondly, that the `ANSIStrings` value works in the same way as its singular counterpart, with a `Display` implementation that only performs the formatting when required. ## Byte strings This library also supports formatting `[u8]` byte strings; this supports applications working with text in an unknown encoding. `Style` and `Colour` support painting `[u8]` values, resulting in an `ANSIByteString`. This type does not implement `Display`, as it may not contain UTF-8, but it does provide a method `write_to` to write the result to any value that implements `Write`: ```rust use ansiterm::Colour::Green; Green.paint("user data".as_bytes()).write_to(&mut std::io::stdout()).unwrap(); ``` Similarly, the type `ANSIByteStrings` supports writing a list of `ANSIByteString` values with minimal escape sequences: ```rust use ansiterm::Colour::Green; use ansiterm::ANSIByteStrings; ANSIByteStrings(&[ Green.paint("user data 1\n".as_bytes()), Green.bold().paint("user data 2\n".as_bytes()), ]).write_to(&mut std::io::stdout()).unwrap(); ``` ansiterm-0.12.2/examples/256_colours.rs000064400000000000000000000031051046102023000157750ustar 00000000000000extern crate ansiterm; use ansiterm::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!(" "); } print!("\n"); for c in 8..16 { glow(c, c != 8); print!(" "); } print!("\n\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!(" "); } print!("\n"); } print!("\n"); // 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!(" "); } print!("\n"); } print!("\n"); // The last greyscale lines for c in 232..=243 { glow(c, false); print!(" "); } print!("\n"); for c in 244..=255 { glow(c, true); print!(" "); } print!("\n"); } 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(&format!(" {:3} ", c))); } ansiterm-0.12.2/examples/basic_colours.rs000064400000000000000000000015001046102023000165370ustar 00000000000000extern crate ansiterm; use ansiterm::{Style, Colour::*}; // 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")); } ansiterm-0.12.2/examples/rgb_colours.rs000064400000000000000000000011231046102023000162310ustar 00000000000000extern crate ansiterm; use ansiterm::{Style, Colour}; // 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(" ")); } print!("\n"); } } ansiterm-0.12.2/src/ansi.rs000064400000000000000000000441141046102023000136230ustar 00000000000000use std::fmt; use crate::style::{Colour, Style}; use crate::write::AnyWrite; use crate::difference::Difference; // ---- generating ANSI codes ---- impl Style { /// Write any bytes that go *before* a piece of text to the given writer. fn write_prefix(&self, f: &mut W) -> Result<(), W::Error> { // If there are actually no styles here, then don’t write *any* codes // as the prefix. An empty ANSI code may not affect the terminal // output at all, but a user may just want a code-free string. if self.is_plain() { return Ok(()); } // Write the codes’ prefix, then write numbers, separated by // semicolons, for each text style we want to apply. write!(f, "\x1B[")?; let mut written_anything = false; { let mut write_char = |c| { if written_anything { write!(f, ";")?; } written_anything = true; #[cfg(feature = "gnu_legacy")] write!(f, "0")?; write!(f, "{}", c)?; Ok(()) }; if self.is_bold { write_char('1')? } if self.is_dimmed { write_char('2')? } if self.is_italic { write_char('3')? } if self.is_underline { write_char('4')? } if self.is_blink { write_char('5')? } if self.is_reverse { write_char('7')? } if self.is_hidden { write_char('8')? } if self.is_strikethrough { write_char('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 { if written_anything { write!(f, ";")?; } written_anything = true; bg.write_background_code(f)?; } if let Some(fg) = self.foreground { if written_anything { write!(f, ";")?; } fg.write_foreground_code(f)?; } // All the codes end with an `m`, because reasons. write!(f, "m")?; Ok(()) } /// Write any bytes that go *after* a piece of text to the given writer. fn write_suffix(&self, f: &mut W) -> Result<(), W::Error> { if self.is_plain() { Ok(()) } else { write!(f, "{}", RESET) } } } /// The code to send to reset all styles and return to `Style::default()`. pub static RESET: &str = "\x1B[0m"; impl Colour { fn write_foreground_code(&self, f: &mut W) -> Result<(), W::Error> { match *self { Colour::Black => write!(f, "30"), Colour::Red => write!(f, "31"), Colour::Green => write!(f, "32"), Colour::Yellow => write!(f, "33"), Colour::Blue => write!(f, "34"), Colour::Purple => write!(f, "35"), Colour::Cyan => write!(f, "36"), Colour::White => write!(f, "37"), Colour::Default => write!(f, "39"), Colour::DarkGray => write!(f, "90"), Colour::BrightRed => write!(f, "91"), Colour::BrightGreen => write!(f, "92"), Colour::BrightYellow => write!(f, "93"), Colour::BrightBlue => write!(f, "94"), Colour::BrightPurple => write!(f, "95"), Colour::BrightCyan => write!(f, "96"), Colour::BrightGray => write!(f, "97"), Colour::Fixed(num) => write!(f, "38;5;{}", &num), Colour::RGB(r,g,b) => write!(f, "38;2;{};{};{}", &r, &g, &b), } } fn write_background_code(&self, f: &mut W) -> Result<(), W::Error> { match *self { Colour::Black => write!(f, "40"), Colour::Red => write!(f, "41"), Colour::Green => write!(f, "42"), Colour::Yellow => write!(f, "43"), Colour::Blue => write!(f, "44"), Colour::Purple => write!(f, "45"), Colour::Cyan => write!(f, "46"), Colour::White => write!(f, "47"), Colour::Default => write!(f, "49"), Colour::DarkGray => write!(f, "100"), Colour::BrightRed => write!(f, "101"), Colour::BrightGreen => write!(f, "102"), Colour::BrightYellow => write!(f, "103"), Colour::BrightBlue => write!(f, "104"), Colour::BrightPurple => write!(f, "105"), Colour::BrightCyan => write!(f, "106"), Colour::BrightGray => write!(f, "107"), Colour::Fixed(num) => write!(f, "48;5;{}", &num), Colour::RGB(r,g,b) => write!(f, "48;2;{};{};{}", &r, &g, &b), } } } /// Like `ANSIString`, but only displays the style prefix. /// /// This type implements the `Display` trait, meaning it can be written to a /// `std::fmt` formatting without doing any extra allocation, and written to a /// string with the `.to_string()` method. For examples, see /// [`Style::prefix`](struct.Style.html#method.prefix). #[derive(Clone, Copy, Debug)] pub struct Prefix(Style); /// Like `ANSIString`, but only displays the difference between two /// styles. /// /// This type implements the `Display` trait, meaning it can be written to a /// `std::fmt` formatting without doing any extra allocation, and written to a /// string with the `.to_string()` method. For examples, see /// [`Style::infix`](struct.Style.html#method.infix). #[derive(Clone, Copy, Debug)] pub struct Infix(Style, Style); /// Like `ANSIString`, but only displays the style suffix. /// /// This type implements the `Display` trait, meaning it can be written to a /// `std::fmt` formatting without doing any extra allocation, and written to a /// string with the `.to_string()` method. For examples, see /// [`Style::suffix`](struct.Style.html#method.suffix). #[derive(Clone, Copy, Debug)] pub struct Suffix(Style); impl Style { /// The prefix bytes for this style. These are the bytes that tell the /// terminal to use a different colour or font style. /// /// # Examples /// /// ``` /// use ansiterm::{Style, Colour::Blue}; /// /// let style = Style::default().bold(); /// assert_eq!("\x1b[1m", /// style.prefix().to_string()); /// /// let style = Blue.bold(); /// assert_eq!("\x1b[1;34m", /// style.prefix().to_string()); /// /// let style = Style::default(); /// assert_eq!("", /// style.prefix().to_string()); /// ``` pub fn prefix(self) -> Prefix { Prefix(self) } /// The infix bytes between this style and `next` style. These are the bytes /// that tell the terminal to change the style to `next`. These may include /// a reset followed by the next colour and style, depending on the two styles. /// /// # Examples /// /// ``` /// use ansiterm::{Style, Colour::Green}; /// /// let style = Style::default().bold(); /// assert_eq!("\x1b[32m", /// style.infix(Green.bold()).to_string()); /// /// let style = Green.normal(); /// assert_eq!("\x1b[1m", /// style.infix(Green.bold()).to_string()); /// /// let style = Style::default(); /// assert_eq!("", /// style.infix(style).to_string()); /// ``` pub fn infix(self, next: Style) -> Infix { Infix(self, next) } /// The suffix for this style. These are the bytes that tell the terminal /// to reset back to its normal colour and font style. /// /// # Examples /// /// ``` /// use ansiterm::{Style, Colour::Green}; /// /// let style = Style::default().bold(); /// assert_eq!("\x1b[0m", /// style.suffix().to_string()); /// /// let style = Green.normal().bold(); /// assert_eq!("\x1b[0m", /// style.suffix().to_string()); /// /// let style = Style::default(); /// assert_eq!("", /// style.suffix().to_string()); /// ``` pub fn suffix(self) -> Suffix { Suffix(self) } } impl Colour { /// The prefix bytes for this colour as a `Style`. These are the bytes /// that tell the terminal to use a different colour or font style. /// /// See also [`Style::prefix`](struct.Style.html#method.prefix). /// /// # Examples /// /// ``` /// use ansiterm::Colour::Green; /// /// assert_eq!("\x1b[0m", /// Green.suffix().to_string()); /// ``` pub fn prefix(self) -> Prefix { Prefix(self.normal()) } /// The infix bytes between this colour and `next` colour. These are the bytes /// that tell the terminal to use the `next` colour, or to do nothing if /// the two colours are equal. /// /// See also [`Style::infix`](struct.Style.html#method.infix). /// /// # Examples /// /// ``` /// use ansiterm::Colour::{Red, Yellow}; /// /// assert_eq!("\x1b[33m", /// Red.infix(Yellow).to_string()); /// ``` pub fn infix(self, next: Colour) -> Infix { Infix(self.normal(), next.normal()) } /// The suffix for this colour as a `Style`. These are the bytes that /// tell the terminal to reset back to its normal colour and font style. /// /// See also [`Style::suffix`](struct.Style.html#method.suffix). /// /// # Examples /// /// ``` /// use ansiterm::Colour::Purple; /// /// assert_eq!("\x1b[0m", /// Purple.suffix().to_string()); /// ``` pub fn suffix(self) -> Suffix { Suffix(self.normal()) } } impl fmt::Display for Prefix { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let f: &mut dyn fmt::Write = f; self.0.write_prefix(f) } } impl fmt::Display for Infix { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match Difference::between(&self.0, &self.1) { Difference::ExtraStyles(style) => { let f: &mut dyn fmt::Write = f; style.write_prefix(f) }, Difference::Reset => { let f: &mut dyn fmt::Write = f; write!(f, "{}{}", RESET, self.1.prefix()) }, Difference::NoDifference => { Ok(()) // nothing to write }, } } } impl fmt::Display for Suffix { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let f: &mut dyn fmt::Write = f; self.0.write_suffix(f) } } #[cfg(feature = "ansi_colours")] impl Colour { /// Constructs `Fixed` colour which approximates given 24-bit sRGB colour. /// /// Tries to find the closest matching colour from the 256-colour ANSI /// pallet and returns fixed colour with that index. /// /// # Examples /// /// ``` /// use ansiterm::Colour; /// /// assert_eq!(Colour::Fixed( 16), Colour::approx_rgb( 0, 0, 0)); /// assert_eq!(Colour::Fixed( 16), Colour::approx_rgb( 0, 1, 2)); /// assert_eq!(Colour::Fixed( 67), Colour::approx_rgb( 95, 135, 175)); /// assert_eq!(Colour::Fixed(231), Colour::approx_rgb(255, 255, 255)); /// ``` pub fn approx_rgb(r: u8, g: u8, b: u8) -> Self { Self::Fixed(ansi_colours::ansi256_from_rgb((r, g, b))) } /// Converts the colour into 256-colour-compatible format. /// /// If the colour is `RGB` converts it into `Fixed` representation /// approximating the 24-bit sRGB colour by an index in the 256-colour ANSI /// palette. Otherwise, returns the colour unchanged. /// /// # Examples /// /// ``` /// use ansiterm::Colour; /// /// assert_eq!(Colour::Red, Colour::Red.into_256()); /// assert_eq!(Colour::Fixed( 11), Colour::Fixed(11).into_256()); /// assert_eq!(Colour::Fixed( 16), Colour::RGB( 0, 0, 0).into_256()); /// assert_eq!(Colour::Fixed( 16), Colour::RGB( 0, 1, 2).into_256()); /// assert_eq!(Colour::Fixed( 67), Colour::RGB( 95, 135, 175).into_256()); /// assert_eq!(Colour::Fixed(231), Colour::RGB(255, 255, 255).into_256()); /// ``` pub fn into_256(self) -> Self { if let Self::RGB(r, g, b) = self { Self::Fixed(ansi_colours::ansi256_from_rgb((r, g, b))) } else { self } } /// Converts the colour into 24-bit sRGB representation. /// /// `RGB` variant is returned unchanged (since they are already in 24-bit /// sRGB representation). /// /// `Fixed` variants are converted by taking corresponding entry from the /// default 256-colour XTerm palette. Note that the first 16 colours /// (so-called system colours) are often configurable by the user so this /// conversion may give different result than what user would see if `Fixed` /// variant was used. /// /// Lastly, `Black` through `White` variants are treated as `Fixed` variant /// with index 0 through 7. /// /// # Examples /// /// ``` /// use ansiterm::Colour; /// /// assert_eq!(( 0, 0, 0), Colour::Fixed( 16).into_rgb()); /// assert_eq!(( 95, 135, 175), Colour::Fixed( 67).into_rgb()); /// assert_eq!((255, 255, 255), Colour::Fixed(231).into_rgb()); /// assert_eq!((238, 238, 238), Colour::Fixed(255).into_rgb()); /// assert_eq!(( 42, 24, 0), Colour::RGB(42, 24, 0).into_rgb()); /// ``` pub fn into_rgb(self) -> (u8, u8, u8) { match self.into_index() { Ok(idx) => ansi_colours::rgb_from_ansi256(idx), Err(rgb) => rgb } } } #[cfg(test)] mod test { use crate::style::Style; use crate::style::Colour::*; macro_rules! test { ($name: ident: $style: expr; $input: expr => $result: expr) => { #[test] fn $name() { assert_eq!($style.paint($input).to_string(), $result.to_string()); let mut v = Vec::new(); $style.paint($input.as_bytes()).write_to(&mut v).unwrap(); assert_eq!(v.as_slice(), $result.as_bytes()); } }; } 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"); test!(brightgreen: BrightGreen; "hi" => "\x1B[92mhi\x1B[0m"); test!(brightred_on_brightblue: BrightRed.on(BrightBlue); "hi" => "\x1B[104;91mhi\x1B[0m"); #[test] fn test_infix() { assert_eq!(Style::new().dimmed().infix(Style::new()).to_string(), "\x1B[0m"); assert_eq!(White.dimmed().infix(White.normal()).to_string(), "\x1B[0m\x1B[37m"); assert_eq!(White.normal().infix(White.bold()).to_string(), "\x1B[1m"); assert_eq!(White.normal().infix(Blue.normal()).to_string(), "\x1B[34m"); assert_eq!(Blue.bold().infix(Blue.bold()).to_string(), ""); } } ansiterm-0.12.2/src/debug.rs000064400000000000000000000112541046102023000137560ustar 00000000000000use std::fmt; use crate::style::Style; /// Styles have a special `Debug` implementation that only shows the fields that /// are set. Fields that haven’t been touched aren’t included in the output. /// /// This behaviour gets bypassed when using the alternate formatting mode /// `format!("{:#?}")`. /// /// ``` /// use ansiterm::Colour::{Red, Blue}; /// /// assert_eq!( /// "Style { fg(Red), on(Blue), bold, italic }", /// format!("{:?}", Red.on(Blue).bold().italic()) /// ); /// ``` impl fmt::Debug for Style { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { if fmt.alternate() { fmt.debug_struct("Style") .field("foreground", &self.foreground) .field("background", &self.background) .field("blink", &self.is_blink) .field("bold", &self.is_bold) .field("dimmed", &self.is_dimmed) .field("hidden", &self.is_hidden) .field("italic", &self.is_italic) .field("reverse", &self.is_reverse) .field("strikethrough", &self.is_strikethrough) .field("underline", &self.is_underline) .finish() } else if self.is_plain() { fmt.write_str("Style {}") } else { fmt.write_str("Style { ")?; let mut written_anything = false; if let Some(fg) = self.foreground { if written_anything { fmt.write_str(", ")? } written_anything = true; write!(fmt, "fg({:?})", fg)? } if let Some(bg) = self.background { if written_anything { fmt.write_str(", ")? } written_anything = true; write!(fmt, "on({:?})", bg)? } { let mut write_flag = |name| { if written_anything { fmt.write_str(", ")? } written_anything = true; fmt.write_str(name) }; if self.is_blink { write_flag("blink")? } if self.is_bold { write_flag("bold")? } if self.is_dimmed { write_flag("dimmed")? } if self.is_hidden { write_flag("hidden")? } if self.is_italic { write_flag("italic")? } if self.is_reverse { write_flag("reverse")? } if self.is_strikethrough { write_flag("strikethrough")? } if self.is_underline { write_flag("underline")? } } write!(fmt, " }}") } } } #[cfg(test)] mod test { use crate::style::Colour::*; use crate::style::Style; fn style() -> Style { Style::new() } macro_rules! test { ($name: ident: $obj: expr => $result: expr) => { #[test] fn $name() { assert_eq!($result, format!("{:?}", $obj)); } }; } test!(empty: style() => "Style {}"); test!(bold: style().bold() => "Style { bold }"); test!(italic: style().italic() => "Style { italic }"); test!(both: style().bold().italic() => "Style { bold, italic }"); test!(red: Red.normal() => "Style { fg(Red) }"); test!(redblue: Red.normal().on(RGB(3, 2, 4)) => "Style { fg(Red), on(RGB(3, 2, 4)) }"); test!(everything: Red.on(Blue).blink().bold().dimmed().hidden().italic().reverse().strikethrough().underline() => "Style { fg(Red), on(Blue), blink, bold, dimmed, hidden, italic, reverse, strikethrough, underline }"); #[test] fn long_and_detailed() { let expected_debug = "Style { fg(Blue), bold }"; let expected_pretty_repat = r##"(?x) Style\s+\{\s+ foreground:\s+Some\(\s+ Blue,?\s+ \),\s+ background:\s+None,\s+ blink:\s+false,\s+ bold:\s+true,\s+ dimmed:\s+false,\s+ hidden:\s+false,\s+ italic:\s+false,\s+ reverse:\s+false,\s+ strikethrough:\s+ false,\s+ underline:\s+false,?\s+ \}"##; let re = regex::Regex::new(expected_pretty_repat).unwrap(); let style = Blue.bold(); let style_fmt_debug = format!("{:?}", style); let style_fmt_pretty = format!("{:#?}", style); println!("style_fmt_debug:\n{}", style_fmt_debug); println!("style_fmt_pretty:\n{}", style_fmt_pretty); assert_eq!(expected_debug, style_fmt_debug); assert!(re.is_match(&style_fmt_pretty)); } } ansiterm-0.12.2/src/difference.rs000064400000000000000000000135461046102023000147700ustar 00000000000000use super::Style; /// When printing out one coloured string followed by another, use one of /// these rules to figure out which *extra* control codes need to be sent. #[derive(PartialEq, Clone, Copy, Debug)] pub enum Difference { /// Print out the control codes specified by this style to end up looking /// like the second string's styles. ExtraStyles(Style), /// Converting between these two is impossible, so just send a reset /// command and then the second string's styles. Reset, /// The before style is exactly the same as the after style, so no further /// control codes need to be printed. NoDifference, } impl Difference { /// Compute the 'style difference' required to turn an existing style into /// the given, second style. /// /// For example, to turn green text into green bold text, it's redundant /// to write a reset command then a second green+bold command, instead of /// just writing one bold command. This method should see that both styles /// use the foreground colour green, and reduce it to a single command. /// /// This method returns an enum value because it's not actually always /// possible to turn one style into another: for example, text could be /// made bold and underlined, but you can't remove the bold property /// without also removing the underline property. So when this has to /// happen, this function returns None, meaning that the entire set of /// styles should be reset and begun again. pub fn between(first: &Style, next: &Style) -> Difference { use self::Difference::*; // XXX(Havvy): This algorithm is kind of hard to replicate without // having the Plain/Foreground enum variants, so I'm just leaving // it commented out for now, and defaulting to Reset. if first == next { return NoDifference; } // Cannot un-bold, so must Reset. if first.is_bold && !next.is_bold { return Reset; } if first.is_dimmed && !next.is_dimmed { return Reset; } if first.is_italic && !next.is_italic { return Reset; } // Cannot un-underline, so must Reset. if first.is_underline && !next.is_underline { return Reset; } if first.is_blink && !next.is_blink { return Reset; } if first.is_reverse && !next.is_reverse { return Reset; } if first.is_hidden && !next.is_hidden { return Reset; } if first.is_strikethrough && !next.is_strikethrough { return Reset; } // Cannot go from foreground to no foreground, so must Reset. if first.foreground.is_some() && next.foreground.is_none() { return Reset; } // Cannot go from background to no background, so must Reset. if first.background.is_some() && next.background.is_none() { return Reset; } let mut extra_styles = Style::default(); if first.is_bold != next.is_bold { extra_styles.is_bold = true; } if first.is_dimmed != next.is_dimmed { extra_styles.is_dimmed = true; } if first.is_italic != next.is_italic { extra_styles.is_italic = true; } if first.is_underline != next.is_underline { extra_styles.is_underline = true; } if first.is_blink != next.is_blink { extra_styles.is_blink = true; } if first.is_reverse != next.is_reverse { extra_styles.is_reverse = true; } if first.is_hidden != next.is_hidden { extra_styles.is_hidden = true; } if first.is_strikethrough != next.is_strikethrough { extra_styles.is_strikethrough = true; } if first.foreground != next.foreground { extra_styles.foreground = next.foreground; } if first.background != next.background { extra_styles.background = next.background; } ExtraStyles(extra_styles) } } #[cfg(test)] mod test { use super::*; use super::Difference::*; use crate::style::Colour::*; use crate::style::Style; fn style() -> Style { Style::new() } macro_rules! test { ($name: ident: $first: expr; $next: expr => $result: expr) => { #[test] fn $name() { assert_eq!($result, Difference::between(&$first, &$next)); } }; } test!(nothing: Green.normal(); Green.normal() => NoDifference); test!(uppercase: Green.normal(); Green.bold() => ExtraStyles(style().bold())); test!(lowercase: Green.bold(); Green.normal() => Reset); test!(nothing2: Green.bold(); Green.bold() => NoDifference); test!(colour_change: Red.normal(); Blue.normal() => ExtraStyles(Blue.normal())); test!(addition_of_blink: style(); style().blink() => ExtraStyles(style().blink())); test!(addition_of_dimmed: style(); style().dimmed() => ExtraStyles(style().dimmed())); test!(addition_of_hidden: style(); style().hidden() => ExtraStyles(style().hidden())); test!(addition_of_reverse: style(); style().reverse() => ExtraStyles(style().reverse())); test!(addition_of_strikethrough: style(); style().strikethrough() => ExtraStyles(style().strikethrough())); test!(removal_of_strikethrough: style().strikethrough(); style() => Reset); test!(removal_of_reverse: style().reverse(); style() => Reset); test!(removal_of_hidden: style().hidden(); style() => Reset); test!(removal_of_dimmed: style().dimmed(); style() => Reset); test!(removal_of_blink: style().blink(); style() => Reset); } ansiterm-0.12.2/src/display.rs000064400000000000000000000215701046102023000143370ustar 00000000000000use std::borrow::Cow; use std::fmt; use std::io; use std::ops::Deref; use crate::ansi::RESET; use crate::difference::Difference; use crate::style::{Style, Colour}; use crate::write::AnyWrite; /// An `ANSIGenericString` includes a generic string type and a `Style` to /// display that string. `ANSIString` and `ANSIByteString` are aliases for /// this type on `str` and `\[u8]`, respectively. #[derive(PartialEq, Debug)] pub struct ANSIGenericString<'a, S: 'a + ToOwned + ?Sized> where ::Owned: fmt::Debug { style: Style, string: Cow<'a, S>, } /// Cloning an `ANSIGenericString` will clone its underlying string. /// /// # Examples /// /// ``` /// use ansiterm::ANSIString; /// /// let plain_string = ANSIString::from("a plain string"); /// let clone_string = plain_string.clone(); /// assert_eq!(clone_string, plain_string); /// ``` impl<'a, S: 'a + ToOwned + ?Sized> Clone for ANSIGenericString<'a, S> where ::Owned: fmt::Debug { fn clone(&self) -> ANSIGenericString<'a, S> { ANSIGenericString { style: self.style, string: self.string.clone(), } } } // You might think that the hand-written Clone impl above is the same as the // one that gets generated with #[derive]. But it’s not *quite* the same! // // `str` is not Clone, and the derived Clone implementation puts a Clone // constraint on the S type parameter (generated using --pretty=expanded): // // ↓_________________↓ // impl <'a, S: ::std::clone::Clone + 'a + ToOwned + ?Sized> ::std::clone::Clone // for ANSIGenericString<'a, S> where // ::Owned: fmt::Debug { ... } // // This resulted in compile errors when you tried to derive Clone on a type // that used it: // // #[derive(PartialEq, Debug, Clone, Default)] // pub struct TextCellContents(Vec>); // ^^^^^^^^^^^^^^^^^^^^^^^^^ // error[E0277]: the trait `std::clone::Clone` is not implemented for `str` // // The hand-written impl above can ignore that constraint and still compile. /// An ANSI String is a string coupled with the `Style` to display it /// in a terminal. /// /// Although not technically a string itself, it can be turned into /// one with the `to_string` method. /// /// # Examples /// /// ``` /// use ansiterm::ANSIString; /// use ansiterm::Colour::Red; /// /// let red_string = Red.paint("a red string"); /// println!("{}", red_string); /// ``` /// /// ``` /// use ansiterm::ANSIString; /// /// let plain_string = ANSIString::from("a plain string"); /// assert_eq!(&*plain_string, "a plain string"); /// ``` pub type ANSIString<'a> = ANSIGenericString<'a, str>; /// An `ANSIByteString` represents a formatted series of bytes. Use /// `ANSIByteString` when styling text with an unknown encoding. pub type ANSIByteString<'a> = ANSIGenericString<'a, [u8]>; impl<'a, I, S: 'a + ToOwned + ?Sized> From for ANSIGenericString<'a, S> where I: Into>, ::Owned: fmt::Debug { fn from(input: I) -> ANSIGenericString<'a, S> { ANSIGenericString { string: input.into(), style: Style::default(), } } } impl<'a, S: 'a + ToOwned + ?Sized> ANSIGenericString<'a, S> where ::Owned: fmt::Debug { /// Directly access the style pub fn style_ref(&self) -> &Style { &self.style } /// Directly access the style mutably pub fn style_ref_mut(&mut self) -> &mut Style { &mut self.style } /// Directly access the string pub fn as_str(&self) -> &S { self.string.as_ref() } } impl<'a, S: 'a + ToOwned + ?Sized> Deref for ANSIGenericString<'a, S> where ::Owned: fmt::Debug { type Target = S; fn deref(&self) -> &S { self.string.deref() } } /// A set of `ANSIGenericString`s collected together, in order to be /// written with a minimum of control characters. #[derive(Debug, PartialEq)] pub struct ANSIGenericStrings<'a, S: 'a + ToOwned + ?Sized> (pub &'a [ANSIGenericString<'a, S>]) where ::Owned: fmt::Debug, S: PartialEq; /// A set of `ANSIString`s collected together, in order to be written with a /// minimum of control characters. pub type ANSIStrings<'a> = ANSIGenericStrings<'a, str>; /// A function to construct an `ANSIStrings` instance. #[allow(non_snake_case)] pub fn ANSIStrings<'a>(arg: &'a [ANSIString<'a>]) -> ANSIStrings<'a> { ANSIGenericStrings(arg) } /// A set of `ANSIByteString`s collected together, in order to be /// written with a minimum of control characters. pub type ANSIByteStrings<'a> = ANSIGenericStrings<'a, [u8]>; /// A function to construct an `ANSIByteStrings` instance. #[allow(non_snake_case)] pub fn ANSIByteStrings<'a>(arg: &'a [ANSIByteString<'a>]) -> ANSIByteStrings<'a> { ANSIGenericStrings(arg) } // ---- paint functions ---- impl Style { /// Paints the given text with this colour, returning an ANSI string. #[must_use] pub fn paint<'a, I, S: 'a + ToOwned + ?Sized>(self, input: I) -> ANSIGenericString<'a, S> where I: Into>, ::Owned: fmt::Debug { ANSIGenericString { string: input.into(), style: self, } } } impl Colour { /// Paints the given text with this colour, returning an ANSI string. /// This is a short-cut so you don’t have to use `Blue.normal()` just /// to get blue text. /// /// ``` /// use ansiterm::Colour::Blue; /// println!("{}", Blue.paint("da ba dee")); /// ``` #[must_use] pub fn paint<'a, I, S: 'a + ToOwned + ?Sized>(self, input: I) -> ANSIGenericString<'a, S> where I: Into>, ::Owned: fmt::Debug { ANSIGenericString { string: input.into(), style: self.normal(), } } } // ---- writers for individual ANSI strings ---- impl<'a> fmt::Display for ANSIString<'a> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let w: &mut dyn fmt::Write = f; self.write_to_any(w) } } impl<'a> ANSIByteString<'a> { /// Write an `ANSIByteString` to an `io::Write`. This writes the escape /// sequences for the associated `Style` around the bytes. pub fn write_to(&self, w: &mut W) -> io::Result<()> { let w: &mut dyn io::Write = w; self.write_to_any(w) } } impl<'a, S: 'a + ToOwned + ?Sized> ANSIGenericString<'a, S> where ::Owned: fmt::Debug, &'a S: AsRef<[u8]> { fn write_to_any + ?Sized>(&self, w: &mut W) -> Result<(), W::Error> { write!(w, "{}", self.style.prefix())?; w.write_str(self.string.as_ref())?; write!(w, "{}", self.style.suffix()) } } // ---- writers for combined ANSI strings ---- impl<'a> fmt::Display for ANSIStrings<'a> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let f: &mut dyn fmt::Write = f; self.write_to_any(f) } } impl<'a> ANSIByteStrings<'a> { /// Write `ANSIByteStrings` to an `io::Write`. This writes the minimal /// escape sequences for the associated `Style`s around each set of /// bytes. pub fn write_to(&self, w: &mut W) -> io::Result<()> { let w: &mut dyn io::Write = w; self.write_to_any(w) } } impl<'a, S: 'a + ToOwned + ?Sized + PartialEq> ANSIGenericStrings<'a, S> where ::Owned: fmt::Debug, &'a S: AsRef<[u8]> { fn write_to_any + ?Sized>(&self, w: &mut W) -> Result<(), W::Error> { use self::Difference::*; let first = match self.0.first() { None => return Ok(()), Some(f) => f, }; write!(w, "{}", first.style.prefix())?; w.write_str(first.string.as_ref())?; for window in self.0.windows(2) { match Difference::between(&window[0].style, &window[1].style) { ExtraStyles(style) => write!(w, "{}", style.prefix())?, Reset => write!(w, "{}{}", RESET, window[1].style.prefix())?, NoDifference => {/* Do nothing! */}, } w.write_str(&window[1].string)?; } // Write the final reset string after all of the ANSIStrings have been // written, *except* if the last one has no styles, because it would // have already been written by this point. if let Some(last) = self.0.last() { if !last.style.is_plain() { write!(w, "{}", RESET)?; } } Ok(()) } } // ---- tests ---- #[cfg(test)] mod tests { pub use super::super::ANSIStrings; pub use crate::style::Style; pub use crate::style::Colour::*; #[test] fn no_control_codes_for_plain() { let one = Style::default().paint("one"); let two = Style::default().paint("two"); let output = format!("{}", ANSIStrings( &[ one, two ] )); assert_eq!(&*output, "onetwo"); } } ansiterm-0.12.2/src/lib.rs000064400000000000000000000235121046102023000134360ustar 00000000000000//! 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 ansiterm::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 //! an [`ANSIString`] value that has a [`Display`] implementation that, when //! formatted, returns the characters. This allows strings to be printed with a //! minimum of [`String`] allocations being performed behind the scenes. //! //! If you *do* want to get at the escape codes, then you can convert the //! [`ANSIString`] to a string as you would any other `Display` value: //! //! ``` //! use ansiterm::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 ansiterm::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 ansiterm::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 ansiterm::Style; //! use ansiterm::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 ansiterm::Style; //! use ansiterm::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 ansiterm::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 ansiterm::Colour::RGB; //! //! RGB(70, 130, 180).paint("Steel blue"); //! ``` //! //! ## Combining successive coloured strings //! //! The benefit of writing ANSI escape codes to the terminal is that they //! *stack*: you do not need to end every coloured string with a reset code if //! the text that follows it is of a similar style. For example, if you want to //! have some blue text followed by some blue bold text, it’s possible to send //! the ANSI code for blue, followed by the ANSI code for bold, and finishing //! with a reset code without having to have an extra one between the two //! strings. //! //! This crate can optimise the ANSI codes that get printed in situations like //! this, making life easier for your terminal renderer. The [`ANSIStrings`] //! type takes a slice of several [`ANSIString`] values, and will iterate over //! each of them, printing only the codes for the styles that need to be updated //! as part of its formatting routine. //! //! The following code snippet uses this to enclose a binary number displayed in //! red bold text inside some red, but not bold, brackets: //! //! ``` //! use ansiterm::Colour::Red; //! use ansiterm::{ANSIString, ANSIStrings}; //! //! let some_value = format!("{:b}", 42); //! let strings: &[ANSIString<'static>] = &[ //! Red.paint("["), //! Red.bold().paint(some_value), //! Red.paint("]"), //! ]; //! //! println!("Value: {}", ANSIStrings(strings)); //! ``` //! //! There are several things to note here. Firstly, the [`paint`] method can take //! *either* an owned [`String`] or a borrowed [`&str`]. Internally, an [`ANSIString`] //! holds a copy-on-write ([`Cow`]) string value to deal with both owned and //! borrowed strings at the same time. This is used here to display a `String`, //! the result of the `format!` call, using the same mechanism as some //! statically-available `&str` slices. Secondly, that the [`ANSIStrings`] value //! works in the same way as its singular counterpart, with a [`Display`] //! implementation that only performs the formatting when required. //! //! ## Byte strings //! //! This library also supports formatting `\[u8]` byte strings; this supports //! applications working with text in an unknown encoding. [`Style`] and //! [`Colour`] support painting `\[u8]` values, resulting in an [`ANSIByteString`]. //! This type does not implement [`Display`], as it may not contain UTF-8, but //! it does provide a method [`write_to`] to write the result to any value that //! implements [`Write`]: //! //! ``` //! use ansiterm::Colour::Green; //! //! Green.paint("user data".as_bytes()).write_to(&mut std::io::stdout()).unwrap(); //! ``` //! //! Similarly, the type [`ANSIByteStrings`] supports writing a list of //! [`ANSIByteString`] values with minimal escape sequences: //! //! ``` //! use ansiterm::Colour::Green; //! use ansiterm::ANSIByteStrings; //! //! ANSIByteStrings(&[ //! Green.paint("user data 1\n".as_bytes()), //! Green.bold().paint("user data 2\n".as_bytes()), //! ]).write_to(&mut std::io::stdout()).unwrap(); //! ``` //! //! [`Cow`]: https://doc.rust-lang.org/std/borrow/enum.Cow.html //! [`Display`]: https://doc.rust-lang.org/std/fmt/trait.Display.html //! [`Default`]: https://doc.rust-lang.org/std/default/trait.Default.html //! [`String`]: https://doc.rust-lang.org/std/string/struct.String.html //! [`&str`]: https://doc.rust-lang.org/std/primitive.str.html //! [`Write`]: https://doc.rust-lang.org/std/io/trait.Write.html //! [`Style`]: struct.Style.html //! [`Style::new()`]: struct.Style.html#method.new //! [`Color`]: enum.Color.html //! [`Colour`]: enum.Colour.html //! [`ANSIString`]: type.ANSIString.html //! [`ANSIStrings`]: type.ANSIStrings.html //! [`ANSIByteString`]: type.ANSIByteString.html //! [`ANSIByteStrings`]: type.ANSIByteStrings.html //! [`write_to`]: type.ANSIByteString.html#method.write_to //! [`paint`]: type.ANSIByteString.html#method.write_to //! [`normal`]: enum.Colour.html#method.normal //! //! [`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 #![crate_name = "ansiterm"] #![crate_type = "rlib"] #![crate_type = "dylib"] #![warn(missing_copy_implementations)] #![warn(missing_docs)] #![warn(trivial_casts, trivial_numeric_casts)] #![warn(unused_extern_crates, unused_qualifications)] #[cfg(target_os="windows")] extern crate winapi; #[cfg(doctest)] #[macro_use] extern crate doc_comment; #[cfg(doctest)] doctest!("../README.md"); mod ansi; pub use crate::ansi::{Prefix, Infix, Suffix}; mod style; pub use crate::style::{Colour, Style}; /// Color is a type alias for `Colour`. pub use crate::Colour as Color; mod difference; mod display; pub use crate::display::*; mod write; mod windows; pub use crate::windows::*; mod util; pub use crate::util::*; mod debug; ansiterm-0.12.2/src/style.rs000064400000000000000000000405011046102023000140250ustar 00000000000000/// A style is a collection of properties that can format a string /// using ANSI escape codes. /// /// # Examples /// /// ``` /// use ansiterm::{Style, Colour}; /// /// let style = Style::new().bold().on(Colour::Black); /// println!("{}", style.paint("Bold on black")); /// ``` #[derive(PartialEq, Clone, Copy)] #[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, /// The style's background colour, if it has one. pub background: Option, /// 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 ansiterm::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 ansiterm::Style; /// /// let style = Style::new().bold(); /// println!("{}", style.paint("hey")); /// ``` pub fn bold(&self) -> Style { Style { is_bold: true, .. *self } } /// Returns a `Style` with the dimmed property set. /// /// # Examples /// /// ``` /// use ansiterm::Style; /// /// let style = Style::new().dimmed(); /// println!("{}", style.paint("sup")); /// ``` pub fn dimmed(&self) -> Style { Style { is_dimmed: true, .. *self } } /// Returns a `Style` with the italic property set. /// /// # Examples /// /// ``` /// use ansiterm::Style; /// /// let style = Style::new().italic(); /// println!("{}", style.paint("greetings")); /// ``` pub fn italic(&self) -> Style { Style { is_italic: true, .. *self } } /// Returns a `Style` with the underline property set. /// /// # Examples /// /// ``` /// use ansiterm::Style; /// /// let style = Style::new().underline(); /// println!("{}", style.paint("salutations")); /// ``` pub fn underline(&self) -> Style { Style { is_underline: true, .. *self } } /// Returns a `Style` with the blink property set. /// # Examples /// /// ``` /// use ansiterm::Style; /// /// let style = Style::new().blink(); /// println!("{}", style.paint("wazzup")); /// ``` pub fn blink(&self) -> Style { Style { is_blink: true, .. *self } } /// Returns a `Style` with the reverse property set. /// /// # Examples /// /// ``` /// use ansiterm::Style; /// /// let style = Style::new().reverse(); /// println!("{}", style.paint("aloha")); /// ``` pub fn reverse(&self) -> Style { Style { is_reverse: true, .. *self } } /// Returns a `Style` with the hidden property set. /// /// # Examples /// /// ``` /// use ansiterm::Style; /// /// let style = Style::new().hidden(); /// println!("{}", style.paint("ahoy")); /// ``` pub fn hidden(&self) -> Style { Style { is_hidden: true, .. *self } } /// Returns a `Style` with the strikethrough property set. /// /// # Examples /// /// ``` /// use ansiterm::Style; /// /// let style = Style::new().strikethrough(); /// println!("{}", style.paint("yo")); /// ``` pub fn strikethrough(&self) -> Style { Style { is_strikethrough: true, .. *self } } /// Returns a `Style` with the foreground colour property set. /// /// # Examples /// /// ``` /// use ansiterm::{Style, Colour}; /// /// let style = Style::new().fg(Colour::Yellow); /// println!("{}", style.paint("hi")); /// ``` pub fn fg(&self, foreground: Colour) -> Style { Style { foreground: Some(foreground), .. *self } } /// Returns a `Style` with the background colour property set. /// /// # Examples /// /// ``` /// use ansiterm::{Style, Colour}; /// /// let style = Style::new().on(Colour::Blue); /// println!("{}", style.paint("eyyyy")); /// ``` pub fn on(&self, background: Colour) -> Style { Style { background: Some(background), .. *self } } /// Return true if this `Style` has no actual styles, and can be written /// without any control characters. /// /// # Examples /// /// ``` /// use ansiterm::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() } } impl Default for Style { /// Returns a style with *no* properties set. Formatting text using this /// style returns the exact same text. /// /// ``` /// use ansiterm::Style; /// assert_eq!(None, Style::default().foreground); /// assert_eq!(None, Style::default().background); /// assert_eq!(false, Style::default().is_bold); /// assert_eq!("txt", Style::default().paint("txt").to_string()); /// ``` fn default() -> Style { Style { foreground: None, background: None, is_bold: false, is_dimmed: false, is_italic: false, is_underline: false, is_blink: false, is_reverse: false, is_hidden: false, is_strikethrough: false, } } } // ---- colours ---- /// 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 #[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, /// Colour #8 (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. DarkGray, /// Colour #9 (foreground code `91`, background code `101`). BrightRed, /// Colour #10 (foreground code `92`, background code `102`). BrightGreen, /// Colour #11 (foreground code `93`, background code `103`). BrightYellow, /// Colour #12 (foreground code `94`, background code `104`). BrightBlue, /// Colour #13 (foreground code `95`, background code `105`). BrightPurple, /// Colour #14 (foreground code `96`, background code `106`). BrightCyan, /// Colour #15 (foreground code `97`, background code `107`). /// /// As above, this is not necessarily the foreground colour, and may be /// hard to read on terminals with light backgrounds. BrightGray, /// 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), /// Default colour (foreground code `39`, background code `49`). // #[default] Default, } impl Colour { /// Returns a `Style` with the foreground colour set to this colour. /// /// # Examples /// /// ``` /// use ansiterm::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 ansiterm::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 ansiterm::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 ansiterm::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 ansiterm::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 ansiterm::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 ansiterm::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 ansiterm::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 ansiterm::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 ansiterm::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() } } /// Returns index in 256-colour ANSI palette or red, green and blue /// components of the colour. /// /// Variants `Black` through `White` are treated as indexes 0 through 7. /// Variant `Fixed` returns the index stored in it. Lastly, `RGB` variant /// is returned as a three-element tuple. pub fn into_index(self) -> Result { match self { Self::Black => Ok(0), Self::Red => Ok(1), Self::Green => Ok(2), Self::Yellow => Ok(3), Self::Blue => Ok(4), Self::Purple => Ok(5), Self::Cyan => Ok(6), Self::White => Ok(7), Self::DarkGray => Ok(8), Self::BrightRed => Ok(9), Self::BrightGreen => Ok(10), Self::BrightYellow => Ok(11), Self::BrightBlue => Ok(12), Self::BrightPurple => Ok(13), Self::BrightCyan => Ok(14), Self::BrightGray => Ok(15), Self::Fixed(idx) => Ok(idx), Self::RGB(r, g, b) => Err((r, g, b)), Self::Default => Ok(16), } } } impl From for Style { /// You can turn a `Colour` into a `Style` with the foreground colour set /// with the `From` trait. /// /// ``` /// use ansiterm::{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::{Style, Colour}; #[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.into_iter() { 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()); } } ansiterm-0.12.2/src/util.rs000064400000000000000000000036041046102023000136450ustar 00000000000000use std::ops::Deref; use crate::display::*; /// Return a substring of the given ANSIStrings sequence, while keeping the formatting. pub fn sub_string(start: usize, len: usize, strs: &ANSIStrings) -> Vec> { let mut vec = Vec::new(); let mut pos = start; let mut len_rem = len; for i in strs.0.iter() { let fragment = i.deref(); let frag_len = fragment.len(); if pos >= frag_len { pos -= frag_len; continue; } if len_rem == 0 { break; } let end = pos + len_rem; let pos_end = if end >= frag_len { frag_len } else { end }; vec.push(i.style_ref().paint(String::from(&fragment[pos..pos_end]))); if end <= frag_len { break; } len_rem -= pos_end - pos; pos = 0; } vec } /// Return a concatenated copy of `strs` without the formatting, as an allocated `String`. pub fn unstyle(strs: &ANSIStrings) -> String { let mut s = String::new(); for i in strs.0.iter() { s += i.deref(); } s } /// Return the unstyled length of ANSIStrings. This is equaivalent to `unstyle(strs).len()`. pub fn unstyled_len(strs: &ANSIStrings) -> usize { let mut l = 0; for i in strs.0.iter() { l += i.deref().len(); } l } #[cfg(test)] mod test { use crate::Colour::*; use crate::display::*; use super::*; #[test] fn test() { let l = [ Black.paint("first"), Red.paint("-second"), White.paint("-third"), ]; let a = ANSIStrings(&l); assert_eq!(unstyle(&a), "first-second-third"); assert_eq!(unstyled_len(&a), 18); let l2 = [ Black.paint("st"), Red.paint("-second"), White.paint("-t"), ]; assert_eq!(sub_string(3, 11, &a).as_slice(), &l2); } } ansiterm-0.12.2/src/windows.rs000064400000000000000000000045541046102023000143670ustar 00000000000000/// 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; use std::iter::once; use std::os::windows::ffi::OsStrExt; use std::ptr::null_mut; use winapi::um::consoleapi::{GetConsoleMode, SetConsoleMode}; use winapi::um::errhandlingapi::GetLastError; use winapi::um::fileapi::{CreateFileW, OPEN_EXISTING}; use winapi::um::handleapi::INVALID_HANDLE_VALUE; use winapi::um::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 = 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(()); } ansiterm-0.12.2/src/write.rs000064400000000000000000000016351046102023000140240ustar 00000000000000use std::fmt; use std::io; pub trait AnyWrite { type Wstr: ?Sized; type Error; fn write_fmt(&mut self, fmt: fmt::Arguments) -> Result<(), Self::Error>; fn write_str(&mut self, s: &Self::Wstr) -> Result<(), Self::Error>; } impl<'a> AnyWrite for dyn fmt::Write + 'a { type Wstr = str; type Error = fmt::Error; fn write_fmt(&mut self, fmt: fmt::Arguments) -> Result<(), Self::Error> { fmt::Write::write_fmt(self, fmt) } fn write_str(&mut self, s: &Self::Wstr) -> Result<(), Self::Error> { fmt::Write::write_str(self, s) } } impl<'a> AnyWrite for dyn io::Write + 'a { type Wstr = [u8]; type Error = io::Error; fn write_fmt(&mut self, fmt: fmt::Arguments) -> Result<(), Self::Error> { io::Write::write_fmt(self, fmt) } fn write_str(&mut self, s: &Self::Wstr) -> Result<(), Self::Error> { io::Write::write_all(self, s) } }