ansiconst

Crates.ioansiconst
lib.rsansiconst
version0.2.1
created_at2024-06-09 06:23:09.35518+00
updated_at2025-11-19 13:20:44.709042+00
descriptionLibrary for declaring nestable ANSI styles in const context
homepage
repositoryhttps://github.com/maccatech/ansiconst-rs
max_upload_size
id1266197
size231,932
Dev (github:maccatech:dev)

documentation

README

ansiconst

Docs Crates.io

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 colors/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::{Ansi, ansi, ansi_code};

// 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!(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 8-bit and RGB colors 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="color256" 8
Ansi feature="rgb" 12
&'static str 16

Simple Macros

Apply ANSI codes using macros:

Examples
use ansiconst::{styled, styled_format, styled_format_args, styled_writeln};
use ansiconst::{paintln, epaintln};

// Notice how "Red" and "Bold" are automatically available inside the macros.
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).to_string();
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 buffer = String::new();
styled_writeln!(&mut buffer, Red, Bold, "My {} is {} years old", pet, age).unwrap();
assert_eq!(buffer, "\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 effectively disabled by setting an important modifier on the outer Ansi's attributes. This works similarly to HTML's CSS !important rule, in that an inner normal attribute is ignored if the same outer attribute is important.

Furthermore, nested Ansis can prevent automatic inheriting of the style attributes of enclosing Ansis by explicitly resetting them using only. Finally, nested Ansis can be disabled entirely with no_ansi.

Examples
use ansiconst::{Styled, styled, styled_format_args};

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

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

// Example 2: inner style only
assert_eq!(
    styled_format_args!(Bold, "Bold {INNER_ONLY} Bold again").to_string(),
    // "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: outer style only + important
assert_eq!(
    styled_format_args!(Bold.only().important(), "Bold {INNER_ONLY} Bold again").to_string(),
    // Entire string is Bold, nested Underline was ignored due to .only().important()
    "\x1B[0;1mBold Inner Bold again\x1B[22m"
);

// Example 4: both important
assert_eq!(
    styled_format_args!(Bold.only().important(), "Bold {INNER_IMPORTANT} Bold again").to_string(),
    // "Inner" is Bold and Underline, due to inner's .important()
    "\x1B[0;1mBold \x1B[4mInner\x1B[24m 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> and StyledString for details.

Examples

use ansiconst::{Ansi, ansi, paintln, styled_format_args};

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, Bold.not());

// 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 {} colors/effects.",
    styled_format_args!(Green, Italic, "way of styling {} i.e. with inline",
        styled_format_args!(Yellow, Bold, "your text,")
    )
);

Upgrading from v0.1.x

Required actions due to breaking changes:

Version History

Version Date Comments
v0.2.1 19-Nov 2025 Enhancement release
  • Add important modifier (similar to CSS !important)
  • Add StyledString to allow overriding ANSI styles inside plain Strings
  • Automatically import colours/effects inside macros
  • Add improved introspection API
  • Improve test coverage
  • Fix some edge cases where suboptimal ANSI codes were emitted to transition between styles
  • Fix not treating identical 4-bit/8-bit/rgb colors as equal
  • Rename Colour to Color
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

Commit count: 7

cargo fmt