#style #ansi #ansi-term #term #color

ansiconst

Library for declaring nestable ANSI styles in const context

2 releases

0.1.1 Jun 10, 2024
0.1.0 Jun 9, 2024

#118 in Command-line interface

Download history 122/week @ 2024-06-03 229/week @ 2024-06-10

351 downloads per month

MIT license

105KB
1.5K SLoC

ansiconst

View the Rustdoc

Contents

Ansi Constants

A library for declaring nestable ANSI styles in const context.

Motivation

The primary motivation of this crate is to provide the ability to use ANSI colours/effects in command-line programs by identifying them semantically rather than literally.

For example, when calling println!, instead of applying the style Green, Bold to the output, it has more semantic meaning to apply a style named Subheading that, when rendered (or "formatted" in Rust), produces the output that activates the Green, Bold styling.

Furthermore, semantic styles should be nestable, like styling frameworks such as HTML's CSS. For example, it should be possible to nest text styled with a name like Manufacturer inside text styled with a name like Product Details and have the literal styles that these semantic styles translate to applied automatically. The nested style's attributes should temporarily layer on top of, or maybe replace, the parent style's attributes as appropriate.

The second key motivation of this crate is to support the above at compile-time. I.e. define semantic styles as const, and then use them with minimal overhead throughout a command-line program.

There are other crates that provide terminal-styling functionality, but none appear to fully support the use-case outlined above. The API provided by this crate in support of this use-case is presented in the following section.

Key Features

Compile-Time

Declare ANSI codes as const. This means the compiler will inline them wherever they are used, potentially improving runtime performance.

Examples
use ansiconst::*;
use ansiconst::Colour::{Green, Blue};
use ansiconst::Effect::{Bold, Underline, Italic};

// Define styles as Ansi structs:
const    HEADING_ANSI: Ansi = ansi!(Green, Bold, Underline);
const SUBHEADING_ANSI: Ansi = ansi!(Blue, Italic);
const      RESET_ANSI: Ansi = Ansi::reset();

assert_eq!(   HEADING_ANSI.to_string(), "\x1B[1;4;32m");
assert_eq!(SUBHEADING_ANSI.to_string(), "\x1B[3;34m");
assert_eq!(     RESET_ANSI.to_string(), "\x1B[0m");

// Or, define styles as ANSI codes:
const    HEADING_CODE: &str = ansi_code!(Green, Bold, Underline);
const SUBHEADING_CODE: &str = ansi_code!(Blue, Italic);
const      RESET_CODE: &str = ansi_code!(Ansi::reset());

assert_eq!(               HEADING_CODE, "\x1B[1;4;32m");
assert_eq!(            SUBHEADING_CODE, "\x1B[3;34m");
assert_eq!(                 RESET_CODE, "\x1B[0m");

Small

Ansi instances are designed to be as small as possible. For example, Effects are represented internally using bit flags rather than simple bools.

For this reason, the use of Ansi256 and Rgb colours is gated behind feature flags, because supporting them means Ansi instances must be ever so slightly bigger. Consider the memory sizes:

Type Bytes
Ansi 6
Ansi feature=Ansi256 8
Ansi feature=Rgb 12
&'static str 16

Simple Macros

Apply ANSI codes using macros:

Examples
use ansiconst::{*, Colour::Red, Effect::Bold};

let pet = "cat";
let age = 5;
let string1 =             styled!(Red, Bold, "My cat is 5 years old").to_string();
let string2 =      styled_format!(Red, Bold, "My {} is {} years old", pet, age);
let string3 = styled_format_args!(Red, Bold, "My {} is {} years old", pet, age).to_string();

assert_eq!(string1, "\x1B[1;31mMy cat is 5 years old\x1B[22;39m");
assert_eq!(string2, "\x1B[1;31mMy cat is 5 years old\x1B[22;39m");
assert_eq!(string3, "\x1B[1;31mMy cat is 5 years old\x1B[22;39m");

// Print "\x1B[1;31mMy cat is 5 years old\x1B[22;39m\n" to stdout and stderr:
paintln! (Red, Bold, "My {} is {} years old", pet, age);
epaintln!(Red, Bold, "My {} is {} years old", pet, age);

// Write "\x1B[1;31mMy cat is 5 years old\x1B[22;39m\n" to a writer:
use std::fmt::Write;
let mut sink = String::new();
styled_writeln!(&mut sink, Red, Bold, "My {} is {} years old", pet, age).unwrap();
assert_eq!(sink, "\x1B[1;31mMy cat is 5 years old\x1B[22;39m\n");

Effortless Nesting

Nesting of ANSI codes is automatically handled, and uses the minimum ANSI code sequences when transitioning between nesting levels.

Additionally, nested ANSI codes can be disabled entirely, or on a per-attribute basis. Parent Ansis can prevent nested Ansis from rendering ANSI codes for any/all attributes by protecting those attributes in the outer Ansi, using methods such as .protect_attrs() and .only().

Examples
use ansiconst::{*, Effect::{Bold, Underline}};

const INNER:           Styled<&str> = styled!(Underline,        "Inner");
const INNER_PROTECTED: Styled<&str> = styled!(Underline.only(), "Inner");

// Example 1: blended styles
assert_eq!(
    styled_format!(Bold, "Bold {INNER} Bold again"),
    // "Inner" is both Bold and Underline
    "\x1B[1mBold \x1B[4mInner\x1B[24m Bold again\x1B[22m"
);

// Example 2: protected inner style
assert_eq!(
    styled_format!(Bold, "Bold {INNER_PROTECTED} Bold again"),
    // "Inner" is not Bold, only Underline, due to inner's .only()
    "\x1B[1mBold \x1B[22;4mInner\x1B[24;1m Bold again\x1B[22m"
);

// Example 3: protected outer style
assert_eq!(
    // Note: outer Bold.only() this time
    styled_format!(Bold.only(), "Bold {INNER} Bold again"),
    // Entire string is Bold, nested Underline was ignored
    "\x1B[1mBold Inner Bold again\x1B[22m"
);

// Example 4: both protected
assert_eq!(
    // Note: Bold.only() again
    styled_format!(Bold.only(), "Bold {INNER_PROTECTED} Bold again"),
    // Entire string is Bold, because outer's .only() takes precedence over inner's
    "\x1B[1mBold Inner Bold again\x1B[22m"
);

Note: automatic handling of nested styles is achieved by storing the last-applied ANSI style in a thread_local! static variable, and therefore this library requires std. See Styled<T> for details.

Examples

use ansiconst::*;
use ansiconst::Colour::{Green, Cyan, Yellow, Purple};
use ansiconst::Effect::{Bold, NotBold, Italic, Underline, Blink};

const HEADING:    Ansi = ansi!(Green, Bold, Underline);
const SUBHEADING: Ansi = ansi!(Cyan, Italic);
const STRONG:     Ansi = ansi!(Yellow, Bold);
const STRONGER:   Ansi = ansi!(Blink);
const STRONGEST:  Ansi = ansi!(Purple, NotBold);

// Styling with paintln!
paintln!(HEADING,    "The Book of Rust");
paintln!();
paintln!(SUBHEADING, "Chapter 1");
paintln!();

// Styling with println!
println!("This sentence shows how {} as you would expect.",
    styled_format_args!(STRONG, "styles can be {}, and they combine",
        styled_format_args!(STRONGER, "nested to {} depths",
            styled_format_args!(STRONGEST, "arbitrary")
        )
    )
);
println!("This sentence shows another {} colours/effects.",
    styled_format_args!(Green, Italic, "way of styling {} i.e. with inline",
        styled_format_args!(Yellow, Bold, "your text,")
    )
);

Version History

Version Date Comments
v0.1.1 10-Jun 2024 Bugfix/docs release
  • Fix handling of FORCE_COLOR, NO_COLOR env vars
  • Fix missing fn for creating AnsiWriter instances
  • Fix broken links in README
  • Add more explanation to Rustdoc
v0.1.0 09-Jun 2024 Initial release

Licence

MIT

Dependencies

~110KB