Skip to content

cli: ANSI color support for validate --text output and STDERR warnings/errors #117

@peczenyj

Description

@peczenyj

Goal

Add ANSI color to the human-readable CLI surfaces of iabtcfv2:

  • validate --text body (status words, reasons, TC strings).
  • STDERR warnings (--enable-warnings output).
  • STDERR plain-text error lines (e.g. "validate: $err\n").

Out of scope by design: any JSON output (dump, default validate, --pretty, --errors-to-stderr JSON records) stays uncolored. JSON consumers should never see ANSI escapes.

Existing rendering (what gets colorized)

_emit_validate in bin/iabtcfv2 emits one of three text shapes today:

OK     <tc> vendor <id>
FAIL   <tc> vendor <id>: <reason>
FAIL   <tc> vendor <id>:
  - <reason 1>
  - <reason 2>
ERROR  <tc>: <error>

STDERR carries warnings (when --enable-warnings is set) and plain text error lines from constructor failures. JSON error records (--errors-to-stderr) are excluded from colorization.

Flag and environment contract

  • New flag: --color=auto|always|never on both dump and validate. (On dump it's effectively a no-op for STDOUT but still controls STDERR warning/error coloring.)
  • Default: auto.
  • Auto-detect logic: STDOUT and STDERR tested independently via -t. Coloring on a stream is enabled only when that stream is a TTY. Piping validate --text | less keeps STDOUT plain; warnings on STDERR can still color if STDERR is a TTY.
  • Honor NO_COLOR env var (no-color.org): when set to any value (including empty), force color off regardless of --color (except --color=always, which wins per spec — actually no, NO_COLOR per spec disables color unconditionally; --color=always SHOULD override only if user explicitly asked for it. Pick one in implementation; the no-color.org spec recommends NO_COLOR is final).
  • Honor CLICOLOR_FORCE env var: when set to a non-empty/non-zero value, force color on even when piped (matches BSD ls, grep --color behavior).
  • Precedence: --color=always/never > NO_COLOR > CLICOLOR_FORCE > TTY auto-detect. Document the precedence in POD.

Color palette (prescribed)

Use Term::ANSIColor (core since Perl 5.6, zero new dependencies).

Element Color
OK status word green bold
FAIL status word red bold
ERROR status word red bold
TC string (the <tc> token in any line) dim
Vendor id (the <id> token) cyan
Reason text (after : or in bullets) default (no color)
Bullet markers - default
STDERR warning lines (full line) yellow
STDERR plain-text error lines (full line) red
JSON output (any subcommand, any flag) never colored

Rationale: the status word is the scannable signal; reasons are the message and must stay readable; TC strings are high-noise and benefit from being de-emphasized.

Implementation notes

  • A small _paint($color, $text) helper that returns the input unchanged when coloring is disabled keeps call sites short and testable.
  • The decision "should I color stream X?" should be computed once per subcommand invocation, after option parsing, separately for STDOUT and STDERR, and stashed in %opts.
  • Term::ANSIColor is loaded only when coloring is enabled, to keep --color=never callers from paying the import cost.
  • Help / --help / short-help output stays uncolored (predictable, scrape-friendly).
  • The dump and validate help blocks (the subcommand_registry rows in bin/iabtcfv2) get a new [--color=WHEN, '', 'Colorize text output (auto, always, never)'] row appended.

Tests

t/10-cli-iabtcfv2.t grows subtests:

  • validate --text --color=never <fixture> → output exactly equals current uncolored output (regression baseline; safe to run in CI without TTY tricks).
  • validate --text --color=always <valid> → output contains the green-bold OK escape sequence.
  • validate --text --color=always <invalid> → output contains the red-bold FAIL escape sequence.
  • validate --text --color=always with NO_COLOR=1 env var → output uncolored (verifies NO_COLOR precedence — or document the chosen precedence and test it).
  • validate --text (no flag) under a piped STDOUT → output uncolored (verifies auto-detect off-when-piped).
  • A subtest forcing TTY emulation is not required — --color=always is sufficient to exercise the colorize path; auto-detection logic is tested via the env-var precedence cases.
  • All existing CLI subtests must keep passing without modification (they don't use --color, so they get the default auto → off-when-piped behavior under prove).

Documentation

  • bin/iabtcfv2 POD documents --color, the precedence rules, and the palette above.
  • A short note at the top of the VALIDATE/Output section explicitly states JSON paths are never colored.
  • xt/spelling.t may need entries for ANSI, colorize, NO_COLOR, CLICOLOR_FORCE.

Out of scope

  • Coloring JSON output (any flavor).
  • Coloring help / --help / short-help output.
  • Theming or custom user palettes (single fixed palette only).
  • Windows-specific ANSI initialization beyond what Term::ANSIColor already does (modern Win10+ terminals support ANSI natively; older cmd.exe falls back to plain).

Definition of done

  • --color=auto|always|never wired into both subcommands; default auto; NO_COLOR and CLICOLOR_FORCE honored.
  • Independent TTY detection on STDOUT and STDERR.
  • Palette matches the table above, applied only to text-mode output and STDERR warnings/errors.
  • No JSON output path emits ANSI escapes under any flag combination.
  • POD documents the flag, precedence, and palette.
  • t/10-cli-iabtcfv2.t covers the subtests listed; existing subtests still pass.
  • prove -lv t/10-cli-iabtcfv2.t passes on the v0.400 floor (Perl 5.8.9).
  • perlcritic, perltidy, xt/spelling.t clean.

References

Metadata

Metadata

Assignees

No one assigned

    Labels

    cliiabtcfv2 command-line toolenhancementNew feature or requestgood first issueGood for newcomershelp wantedExtra attention is needed

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions