Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
# woad.Rust - Changes <!-- omit in toc -->


## 0.1.0-alpha1 - 22nd October 2026

* Colour policy from `WOAD_COLOUR` / `WOAD_COLOR`, `NO_COLOR`, `CLICOLOR_FORCE`, and `CLICOLOR`, cached until `refresh()`;
* Stream-conditional sequences (`seq_for`, `supports_colour`, attribute helpers) for `IsTerminal` destinations, plus Unix `AsFd` equivalents of the descriptor API;
* Packed `version()` in the **woad** `WOAD_VER` layout (`0x41` alpha 1) and `version_string()`;
* Attribute sequences `BOLD` and `BLINKING`;
* Windows console-mode and handle currencies are left for a later alpha;


## 0.0.1 - 12th September 2026

* SGR colour and reset codes (`RESET`, `FG_*`, `BG_*`, including bright variants);
Expand Down
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 7 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ authors = [
categories = [
"command-line-interface",
]
description = "Minimal ANSI terminal colour codes (for Rust)"
description = "Minimal ANSI terminal colour codes, with stream-conditional emission (for Rust)"
documentation = "https://docs.rs/woad"
edition = "2021"
exclude = [
Expand All @@ -26,6 +26,7 @@ homepage = "https://github.com/synesissoftware/woad.Rust"
keywords = [
"ansi",
"colour",
"no-color",
"sgr",
"terminal",
"tty",
Expand All @@ -35,7 +36,7 @@ name = "woad"
readme = "README.md"
repository = "https://github.com/synesissoftware/woad.Rust"
rust-version = "1.74"
version = "0.0.1"
version = "0.1.0-alpha1"


# ##########################################################
Expand All @@ -45,6 +46,10 @@ version = "0.0.1"
name = "woad"
path = "src/lib.rs"

[[example]]
name = "colour_for"
path = "examples/colour_for/main.rs"

[[example]]
name = "versions"
path = "examples/versions/main.rs"
Expand Down
6 changes: 4 additions & 2 deletions EXAMPLES.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
# woad.Rust - Examples <!-- omit in toc -->


| Name | Source | Summary |
| ---- | ------ | ------- |
| Name | Source | Summary |
| -------------- | -------------------------------------------------------------- | ---------------------------------------------------- |
| **colour_for** | [examples/colour_for/main.rs](./examples/colour_for/main.rs) | Emits colour only when the destination warrants it |
| **versions** | [examples/versions/main.rs](./examples/versions/main.rs) | Prints `version_string()` and packed `version()` |


<!-- ########################### end of file ########################### -->
9 changes: 5 additions & 4 deletions NEWS.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
# woad.Rust - News <!-- omit in toc -->


| Date | News Item | Details |
| ---------------- | --------- | --------------------------- |
| 12th September 2026 | [**woad.Rust** 0.0.1](https://github.com/synesissoftware/woad.Rust/releases/tag/0.0.1) | SGR colour and reset codes |
| 15th August 2026 | [**woad.Rust** 0.0.0](https://github.com/synesissoftware/woad.Rust/releases/tag/0.0.0) | Initial project scaffolding |
| Date | News Item | Details |
| ------------------- | --------------------------- | ------- |
| 22nd October 2026 | [**woad.Rust** 0.1.0-alpha1](https://github.com/synesissoftware/woad.Rust/releases/tag/0.1.0-alpha1) | Stream-conditional `seq_for` and colour policy |
| 12th September 2026 | [**woad.Rust** 0.0.1](https://github.com/synesissoftware/woad.Rust/releases/tag/0.0.1) | SGR colour and reset codes |
| 15th August 2026 | [**woad.Rust** 0.0.0](https://github.com/synesissoftware/woad.Rust/releases/tag/0.0.0) | Initial project scaffolding |


<!-- ########################### end of file ########################### -->
66 changes: 62 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,13 @@ Minimal ANSI terminal colour codes, for Rust
## Table of Contents <!-- omit in toc -->

- [Introduction](#introduction)
- [Developer Experience](#developer-experience)
- [Installation](#installation)
- [Components](#components)
- [Fixed sequences](#fixed-sequences)
- [Stream-conditional sequences](#stream-conditional-sequences)
- [Colour policy](#colour-policy)
- [Version](#version)
- [Project Information](#project-information)
- [Where to get help](#where-to-get-help)
- [Contribution guidelines](#contribution-guidelines)
Expand All @@ -29,23 +34,38 @@ Minimal ANSI terminal colour codes, for Rust

## Introduction

**woad** provides the smallest useful set of fixed ANSI SGR colour sequences for library authors. It is not a console or TUI framework.
**woad** provides fixed ANSI SGR sequences, and stream-conditional counterparts that emit only when the destination warrants colour. It is not a console or TUI framework.

**woad.Rust** is the **Rust** implementation.
**woad.Rust** is the **Rust** implementation. This alpha matches the dynamic API of **woad** off Windows. Console-mode changes and Windows handle currencies are not included.


## Developer Experience

**woad.Rust** builds with Cargo. The minimum supported Rust version is **1.74**.

```bash
cargo test
cargo run --example colour_for
```

`colour_for` prints colour only when stdout or stderr warrants it. Pipe it through `cat`, then repeat with `WOAD_COLOUR=always` and `WOAD_COLOUR=never`. Fixed sequences need no policy check. The conditional helpers are under [Components](#components).


## Installation

Reference in **Cargo.toml** in the usual way:

```toml
woad = { version = "0" }
woad = { version = "0.1.0-alpha1" }
```


## Components

**woad.Rust** ships SGR string constants (`RESET`, `FG_*`, `BG_*`, including bright variants) and `VERSION`. TTY/stream gating and Windows virtual-terminal opt-in are not implemented yet.

### Fixed sequences

String constants: `RESET`, `BOLD`, `BLINKING`, `FG_*`, and `BG_*`, the colours each in normal and `BRIGHT` variants. They always emit.

```rust
use woad::{FG_GREEN, RESET};
Expand All @@ -56,6 +76,44 @@ fn main() {
```


### Stream-conditional sequences

`seq_for`, `fg_for`, and `bg_for` yield the sequence when the destination warrants colour, and `""` when it does not. `reset_for`, `bold_for`, and `blinking_for` are the attribute forms. The destination is any [`std::io::IsTerminal`](https://doc.rust-lang.org/std/io/trait.IsTerminal.html) value (`Stdout`, `Stderr`, `Stdin`, `File`, and on Unix a borrowed file descriptor).

```rust
let stdout = std::io::stdout();

println!(
"{}ok{}",
woad::seq_for(woad::FG_GREEN, &stdout),
woad::reset_for(&stdout),
);
```

On Unix, `fd_is_tty`, `fd_supports_colour`, and `seq_for_fd` take [`AsFd`](https://doc.rust-lang.org/std/os/fd/trait.AsFd.html). A missing destination is `supports_colour_absent` / `seq_for_absent` (the C null-stream case: the policy alone).


### Colour policy

The environment is read once, on first need, until `refresh()`:

| Variable | Value | Effect |
| --- | --- | --- |
| `WOAD_COLOUR`, or `WOAD_COLOR` | `1`, `always`, `force`, `on`, `true`, `yes` | Force colour |
| `WOAD_COLOUR`, or `WOAD_COLOR` | `0`, `false`, `never`, `no`, `none`, `off` | Inhibit colour |
| `WOAD_COLOUR`, or `WOAD_COLOR` | `auto`, or anything unrecognised | Defer to the destination |
| `NO_COLOR` | set and non-empty | Inhibit colour |
| `CLICOLOR_FORCE` | set, non-empty, and not `0` | Force colour |
| `CLICOLOR` | `0` | Inhibit colour |

`WOAD_COLOUR` / `WOAD_COLOR` are case-insensitive. In the deferring case, a `TERM` of `dumb` also suppresses colour. `console_supports_ansi()` is always `true` in this alpha.


### Version

`VERSION` and `version_string()` are the SemVer string, including the prerelease label (`0.1.0-alpha1`). `version()` is the packed **woad** `WOAD_VER` (`0x00010041`; low byte `0x41` is alpha 1).


## Project Information


Expand Down
4 changes: 2 additions & 2 deletions TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@
## Functional improvements

* [x] ~~~SGR colour and reset codes~~~ - ✅;
* [ ] TTY-conditional colour codes (process and per-stream);
* [ ] Windows virtual-terminal gating (OS build + `GetConsoleMode`);
* [x] ~~~TTY-conditional colour codes (process and per-stream)~~~ - ✅;
* [ ] Windows virtual-terminal gating (OS build + `GetConsoleMode`) and handle currencies;


## Performance improvements
Expand Down
60 changes: 60 additions & 0 deletions examples/colour_for/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
// Emits colour only when the destination warrants it.
//
// cargo run --example colour_for
// cargo run --example colour_for | cat
// WOAD_COLOUR=always cargo run --example colour_for | cat
// WOAD_COLOUR=never cargo run --example colour_for

fn main() {
let stdout = std::io::stdout();
let stderr = std::io::stderr();

println!(
"policy: {}; stdout is{} a tty; stderr is{} a tty",
woad::colour_policy(),
if woad::stream_is_tty(&stdout) { "" } else { " not" },
if woad::stream_is_tty(&stderr) { "" } else { " not" },
);

println!(
"{}this line is green if, and only if, stdout warrants colour{}",
woad::seq_for(woad::FG_GREEN, &stdout),
woad::reset_for(&stdout),
);

println!(
"{}this line is bold if, and only if, stdout warrants colour{}",
woad::bold_for(&stdout),
woad::reset_for(&stdout),
);

println!(
"{}this line blinks if, and only if, stdout warrants colour{}",
woad::blinking_for(&stdout),
woad::reset_for(&stdout),
);

eprintln!(
"{}{}this line is white-on-red if, and only if, stderr warrants colour{}",
woad::bg_for(woad::BG_RED, &stderr),
woad::fg_for(woad::FG_BRIGHT_WHITE, &stderr),
woad::reset_for(&stderr),
);

println!(
"{}this line is yellow if, and only if, stdout warrants colour{}",
woad::fg_for(woad::FG_YELLOW, &stdout),
woad::reset_for(&stdout),
);

#[cfg(unix)]
{
use std::os::fd::AsFd as _;

println!(
"{}this line is cyan if, and only if, stdout's descriptor warrants colour{}",
woad::seq_for_fd(woad::FG_CYAN, stdout.as_fd()),
woad::seq_for_fd(woad::RESET, stdout.as_fd()),
);
}
}
10 changes: 9 additions & 1 deletion examples/versions/main.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
fn main() {
println!("{} v{}", env!("CARGO_PKG_NAME"), env!("CARGO_PKG_VERSION"),);
let stdout = std::io::stdout();

println!(
"{}v{}{} ({:#010x})",
woad::seq_for(woad::FG_GREEN, &stdout),
woad::version_string(),
woad::reset_for(&stdout),
woad::version(),
);
}
Loading
Loading