northrook/ansi-formatter

Format strings with ANSI colour codes for terminal output based on embedded tags

Maintainers

Package info

codeberg.org/northrook/ansi-formatter

Issues

pkg:composer/northrook/ansi-formatter

Transparency log

Statistics

Installs: 10

Dependents: 3

Suggesters: 0

dev-main 2026-07-08 08:18 UTC

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

TagEffect
<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

InputOutput
\{%}{%}
\{%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();
MethodDescription
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:

ModeSTDERR behaviour
IGNORENever writes to STDERR
STDERRFlushes warnings at the end of each string() call
DEFERQueues warnings; flushes on writeUnsupportedToStderr(), AnsiOutput::output(), or formatter destruct

Default when constructing AnsiFormatter directly:

Logger providedDefault mode
NoSTDERR
YesIGNORE

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.