northrook / ansi-formatter
Format strings with ANSI colour codes for terminal output based on embedded tags
Requires
- php: >=8.4
- psr/log: ^3.0
Requires (Dev)
- northrook/php-cs: dev-main
- phpstan/phpstan: ^2.2
- phpunit/phpunit: ^11.0
This package is not auto-updated.
Last update: 2026-07-09 05:28:25 UTC
README
Format strings with ANSI colour codes for terminal output based on lightweight markup tags.
Requires PHP 8.4+.
Installation
composer require northrook/ansi-formatter
Quick start
use Northrook\AnsiFormatter;
$format = new AnsiFormatter();
echo $format->string('<blue b>Hello</blue> <b>World</b>!');
When STDOUT is a TTY, tags become ANSI escape codes.
When output is piped or redirected, tags are stripped and no codes are emitted.
TTY detection uses stream_isatty(STDOUT); pass assumeTTY: true or assumeTTY: false to override.
Markup reference
Tags are case-insensitive and may be nested.
Combine styles with attributes on the opening tag.
Styles
| Tag | Effect |
|---|---|
<b> | Bold |
<dim> | Dim |
<i> | Italic |
<u> | Underline |
<blink> | Blink |
<reverse> | Reverse video |
<conceal> | Concealed |
Foreground colours
black, red, green, yellow, blue, magenta, cyan, white, gray
Background colours
bg-black, bg-red, bg-green, bg-yellow, bg-blue, bg-magenta, bg-cyan, bg-white
Examples
$format->string('<blue b>Hello</blue>'); // bold blue
$format->string('<red bg-blue>Alert</red>'); // red on blue background
$format->string('<blue><b>hi</b> there</blue>'); // nested styles
Close tags are required. A missing close tag is logged as a warning and the style is not reset at the end of the element.
Non-TTY output
When output is not a TTY:
- Tag-shaped markup is removed from the result
- No ANSI escape codes are emitted
- Unsupported-markup warnings are suppressed (even when a logger is configured)
This keeps piped and log-captured output clean.
Literal < characters
A < is only parsed as markup when it appears at the start of the string or is not immediately preceded by a letter, digit, ., or _.
Everything else stays literal.
$format->string('array<int>'); // array<int>
$format->string('Array.<int>'); // Array.<int>
$format->string('Generic<Foo>'); // Generic<Foo>
$format->string('2 < 3'); // 2 < 3
$format->string(' <blue>x</blue>'); // styled (space before <)
Dynamic content
Pass dynamic values as arguments so they are inserted raw and never parsed as markup.
// Positional
$format->string('<red>Error:</red> {%}', $userMessage);
// Named
$format->string('<red>{%label}</red> {%detail}', label: 'Error:', detail: $userMessage);
// Placeholder in a tag attribute
$format->string('<blue {%style}>hi</blue>', style: 'b');
{%} consumes positional arguments in order. {%name} looks up a named argument.
Values are inserted verbatim — they are not escaped and may contain ANSI sequences or angle brackets.
Only pass trusted content through placeholders.
Use the template for markup; use placeholders for data.
Escaping
| Input | Output |
|---|---|
\{%} | {%} |
\{%name} | {%name} |
\\ | \ |
Without matching arguments, {%} and {%name} in the template stay literal.
AnsiOutput
AnsiOutput buffers multiple formatted fragments and joins them on output().
Use it when building multi-line CLI output.
use Northrook\AnsiFormatter\AnsiOutput;
$out = new AnsiOutput();
$out
->line('<blue>Starting</blue>')
->line('<green>Done</green>');
echo $out->output();
| Method | Description |
|---|---|
append() | Add a formatted fragment to the end |
prepend() | Add a formatted fragment to the start |
line() | Append a newline (if non-empty) then a fragment |
output() | Join fragments; caches until the buffer changes |
__toString() | Same as output(flush: true) |
The object is callable — $out('<red>x</red>') delegates to append().
When no formatter is injected, one is created with deferred STDERR flushing (see below).
Pass your own AnsiFormatter instance to customise logger, TTY, or warning behaviour.
Post-processor
Apply a callback to each fragment before joining:
$out = new AnsiOutput(
postProcessor: static fn (string $chunk, int $index): string => "[{$index}]{$chunk}",
);
Unsupported markup
Unknown tags and attributes are stripped from the output. Missing or mismatched close tags are also reported.
When output is a TTY, a warning is logged via the configured PSR-3 logger.
Control STDERR output with OnUnsupported:
| Mode | STDERR behaviour |
|---|---|
IGNORE | Never writes to STDERR |
STDERR | Flushes warnings at the end of each string() call |
DEFER | Queues warnings; flushes on writeUnsupportedToStderr(), AnsiOutput::output(), or formatter destruct |
Default when constructing AnsiFormatter directly:
| Logger provided | Default mode |
|---|---|
| No | STDERR |
| Yes | IGNORE |
AnsiOutput autovivifies a formatter with DEFER so warnings from multiple fragments are written once on output().
use Northrook\AnsiFormatter\OnUnsupported;
use Psr\Log\LoggerInterface;
$format = new AnsiFormatter(
logger: $logger,
onUnsupported: OnUnsupported::STDERR,
assumeTTY: true,
);
Flush a deferred queue manually:
$format->writeUnsupportedToStderr();
Development
composer test
composer phpstan
License
BSD-3-Clause. See LICENSE.