Text::ANSI::Util(3pm)

πŸ“¦
version 0.234 Β· 3 versions
, in debian trixie, ubuntu noble, ubuntu resolute, ubuntu stonking

Text::ANSI::Util πŸ’–

okay bestie, welcome to Text::ANSI::Util(3pm) β€” it's a User Contributed Perl Documentation page, and it's literally the glow-up your terminal text needed. We're talking routines for text that has ANSI color codes all up in it. Periodt.


✨ NAME ✨

Text::ANSI::Util β€” Routines for text containing ANSI color codes πŸŽ€

🌸 VERSION 🌸

This document describes version 0.234 of Text::ANSI::Util (from Perl distribution Text-ANSI-Util), released on 2023-02-27.


πŸ¦‹ SYNOPSIS πŸ¦‹

use Text::ANSI::Util qw(
                      ta_add_color_resets
                      ta_detect
                      ta_extract_codes
                      ta_highlight
                      ta_highlight_all
                      ta_length
                      ta_length_height
                      ta_pad
                      ta_split_codes
                      ta_split_codes_single
                      ta_strip
                      ta_substr
                      ta_trunc
                      ta_wrap
                     );

# detect whether text has ANSI color codes?
say ta_detect("red");       # => false
say ta_detect("\e[31mred"); # => true

# calculate length of text (excluding the ANSI color codes)
say ta_length("red");       # => 3
say ta_length("\e[31mred"); # => 3

# strip ANSI color codes
say ta_strip("\e[31mred"); # => "red"

# split codes (ANSI color codes are always on the even positions)
my @parts = ta_split_codes("\e[31mred"); # => ("", "\e[31m", "red")

# wrap text to a certain column width, handle ANSI color codes
say ta_wrap("....", 40);

# pad (left, right, center) text to a certain width
say ta_pad("foo", 10);                          # => "foo       "
say ta_pad("foo", 10, "left");                  # => "       foo"
say ta_pad("foo\nbarbaz\n", 10, "center", "."); # => "...foo....\n..barbaz..\n"

# truncate text to a certain width while still passing ANSI color codes
use Term::ANSIColor;
my $text = color("red")."red text".color("reset"); # => "\e[31mred text\e[0m"
say ta_trunc($text, 5);                            # => "\e[31mred t\e[0m"

# highlight the first occurrence of some string within text
say ta_highlight("some text", "ome", "\e[7m\e[31m");

# ditto, but highlight all occurrences
say ta_highlight_all(...);

# get substring
my $substr = ta_substr("...", $pos, $len);

# return text but with substring replaced with replacement
say ta_substr("...", $pos, $len, $replacement);

πŸ’… DESCRIPTION πŸ’…

So this module hands you routines for dealing with text that's got ANSI color codes sprinkled all through it β€” like figuring out how long/wide it actually is (color codes don't count, bestie πŸ’€), stripping the codes out, pulling the codes out on their own, all of that.

If you need the functions that also do wide characters, go see Text::ANSI::WideUtil β€” that's a whole separate vibe.

Current caveats (read these, they're giving "important"):

  • Other ANSI codes (non-color codes) are ignored β€” we're talking codes like the ones for moving the cursor around and stuff.
  • Single-character CSI (control sequence introducer) currently ignored β€” only ESC+[ (two-character CSI) is parsed right now. BTW, in ASCII terminals, single-character CSI is 0x9b. In UTF-8 terminals, it's 0xc2, 0x9b (2 bytes). iykyk.
  • Private-mode- and trailing-intermediate character currently not parsed
  • Only color reset code \e[0m is recognized β€” for simplicity, multiple SGR parameters inside a single ANSI color code are not parsed right now. Meaning color reset codes like "\e[1;0m" or "\e[31;47;0m" don't get recognized, only "\e[0m" does. The author believes this shouldn't be a problem with most real-world text out there, and honestly? fair.

πŸ”₯ FUNCTIONS πŸ”₯

ta_add_color_resets(@text) => LIST 🫢

This one makes sure a color reset command (adds "\e[0m") gets tacked onto the end of every element, and a replay of all the color codes from the previous element (from the last color reset) gets slapped onto the start of the next element, and so on and so on. Returns the new list. ✨

This is the move that makes each element safe to combine with other arrays of text into a single line β€” say, in a multicolumn/tabular layout. Example time:

Without color resets:

my @col1 = split /\n/, "\e[31mred\nmerah\e[0m";
my @col2 = split /\n/, "\e[32mgreen\e[1m\nhijau tebal\e[0m";

printf "%s | %s\n", $col1[0], $col2[0];
printf "%s | %s\n", $col1[1], $col2[1];

the printed output:

\e[31mred | \e[32mgreen
merah\e[0m | \e[1mhijau tebal\e[0m

See how the "merah" text on the second line becomes green? 😭 That's because of the effect of the last color command printed ("\e[32m"). Not the vibe we want. But with ta_add_color_resets():

my @col1 = ta_add_color_resets(split /\n/, "\e[31mred\nmerah\e[0m");
my @col2 = ta_add_color_resets(split /\n/, "\e[32mgreen\e[1m\nhijau tebal\e[0m");

printf "%s | %s\n", $col1[0], $col2[0];
printf "%s | %s\n", $col1[1], $col2[1];

the printed output β€” and the "<...>" marks the code that ta_add_color_resets() added:

\e[31mred<\e[0m> | \e[32mgreen\e[1m<\e[0m>
<\e[31m>merah\e[0m | <\e[32m\e[1m>hijau tebal\e[0m

All the cells print with the intended colors. Iconic. πŸ‘‘

ta_detect($text) => BOOL πŸ’–

Returns true if $text contains ANSI color codes, false otherwise. Simple as.

ta_extract_codes($text) => STR πŸŽ€

This is the opposite of "ta_strip()" β€” it returns only the ANSI codes in $text.

ta_highlight($text, $needle, $color) => STR 🌈

Highlights the first occurrence of $needle in $text with <$color>, being super careful not to mess up the colors that are already there.

$needle can be a string or a Regexp object.

Implementation note, because this part is lowkey genius: to not mess up colors, we save up all the color codes from the last reset ("\e[0m") before inserting the highlight color + highlight text. Then we issue "\e[0m" and the saved-up color code to return back to the color state that existed before the highlight got inserted. Same technique as described in "ta_add_color_resets()". Slay.

ta_highlight_all($text, $needle, $color) => STR πŸ’œ

Like "ta_highlight()", but highlights all the occurrences instead of only the first. Obsessed.

ta_length($text) => INT 🌸

Counts the number of characters in $text, while ignoring ANSI color codes. Equivalent to "length(ta_strip($text))". See also: "ta_mbswidth()" in Text::ANSI::WideUtil.

ta_length_height($text) => [INT, INT] 🧚

Like "ta_length()", but also gives you the height (number of lines). For example, "ta_length_height("foobar\nb\n")" gives "[6, 3]".

See also: "ta_mbswidth_height()" in Text::ANSI::WideUtil.

ta_pad($text, $width[, $which[, $padchar[, $truncate]]]) => STR πŸŽ€

Returns $text padded with $padchar to $width columns. $which is either "r" or "right" for padding on the right (this is the default if you don't specify anything), "l" or "left" for padding on the right, or "c" or "center" or "centre" for left+right padding to center the text.

$padchar is whitespace if not specified. It should be a string having the width of 1 column.

⚠️ Does not handle multiline text β€” you can split text by "/\r?\n/" yourself, bestie.

See also: "ta_mbpad()" in Text::ANSI::WideUtil.

ta_split_codes($text) => LIST ⭐

Splits $text into a list containing alternating ANSI color codes and text. The ANSI color codes are always on the second element, the fourth, and so on. Example:

ta_split_codes("");              # => ()
ta_split_codes("a");             # => ("a")
ta_split_codes("a\e[31m");       # => ("a", "\e[31m")
ta_split_codes("\e[31ma");       # => ("", "\e[31m", "a")
ta_split_codes("\e[31ma\e[0m");  # => ("", "\e[31m", "a", "\e[0m")
ta_split_codes("\e[31ma\e[0mb"); # => ("", "\e[31m", "a", "\e[0m", "b")
ta_split_codes("\e[31m\e[0mb");  # => ("", "\e[31m\e[0m", "b")

so you can do something like:

my @parts = ta_split_codes($text);
while (my ($text, $ansicode) = splice(@parts, 0, 2)) {
    ...
}

ta_split_codes_single($text) => LIST πŸ¦‹

Like "ta_split_codes()" but each ANSI color code gets split separately, instead of being grouped together. This routine is currently used internally, e.g. for "ta_wrap()" and "ta_highlight()", to trace color reset/replay codes.

ta_strip($text) => STR πŸ’…

Strips ANSI color codes from $text, returning the stripped text. Clean girl aesthetic.

ta_substr($text, $pos, $len[ , $replacement ]) => STR 🌈

A bit like Perl's "substr()". If $replacement is not specified, it'll return the substring. If $replacement is specified, it'll return $text with the substring replaced by $replacement.

See also: "ta_mbsubstr()" in Text::ANSI::WideUtil.

ta_trunc($text, $width) => STR βœ‚οΈ

Truncates $text to $width columns while still including all the ANSI color codes. This ensures the truncated text still resets colors, etc.

⚠️ Does not handle multiline text β€” split text by "/\r?\n/" yourself, fr fr.

See also: "ta_mbtrunc()" in Text::ANSI::WideUtil.

ta_wrap($text, $width, %opts) => STR πŸ”₯

Like Text::WideChar::Util's "wrap()", except it handles ANSI color codes. It performs a color reset at the end of each line and a color replay at the start of the subsequent line, so the text is safe for combining in a multicolumn/tabular layout. Main character energy only.

Options:

  • flindent => STR β€” First line indent. Currently must not contain ANSI color codes or wide characters.
  • slindent => STR β€” Subsequent line indent. Currently must not contain ANSI color codes or wide characters.
  • tab_width => INT (default: 8)
  • pad => BOOL (default: 0) β€” If set to true, it'll pad each line to $width. This is convenient if you need the lines padded β€” saves you calls to ta_pad().
  • return_stats => BOOL (default: 0) β€” If set to true, then instead of returning the wrapped string, the function returns "[$wrapped, $stats]" where $stats is a hash containing some information like "max_word_width", "min_word_width".
  • keep_trailing_space => BOOL (default: 0) β€” If set to true, then the trailing space that separates words will be kept at the end of wrapped lines. This option is useful if you want to rejoin the lines later. Without this option set to true, wrapping this line at width=4 (quotes shown):
"some long   line"

will result in:

"some"
"long"
"line"

While if this option is set to true, the result will be:

"some "
"long "
"line"

Performance: ~500/s on the author's Core i5 1.7GHz laptop for a ~1KB of text (with zero to moderate amount of color codes).

See also: "ta_mbwrap()" in Text::ANSI::WideUtil.


πŸ’€ FAQ πŸ’€

Why split functionalities of wide character and color support into multiple modules/distributions?

Performance (see the numbers in the function description), dependency (Unicode::GCString is used for wide character support), and overhead (loading Unicode::GCString).

How do I highlight a string case-insensitively?

You can currently use a regex for the $needle and use the "i" modifier. Example:

use Term::ANSIColor;
ta_highlight($text, qr/\b(foo)\b/i, color("bold red"));

🌟 HOMEPAGE 🌟

Please visit the project's homepage at <https://metacpan.org/release/Text-ANSI-Util>. 🫢

πŸŽ€ SOURCE πŸŽ€

Source repository is at <https://github.com/perlancar/perl-Text-ANSI-Util>. πŸ‘‘

🌈 SEE ALSO ✨

okay so if Text::ANSI::Util isn't quite your girl, here's her friend group πŸ’–:

  • String::Pad β€” gives you a padding function for strings that have no ASCII escape codes and no wide characters. clean, simple, no drama.
  • Text::NonWideChar::Util β€” has some other functions for strings that also have no ASCII escape codes and no wide characters. same vibe, different toolbox 🧰
  • Text::WideChar::Util β€” utilities for strings that have no ANSI escape codes but do have wide characters πŸ‘€
  • Text::ANSI::WideUtil β€” utilities for the full chaos combo: strings that have ANSI escape codes and wide characters πŸ”₯

πŸ‘‘ AUTHOR

perlancar <perlancar@cpan.org> β€” the icon, the legend, the one who wrote all this πŸ’…

🫢 CONTRIBUTOR

Steven Haryanto <stevenharyanto@gmail.com> β€” bestie showed up and helped, we love to see it 🌸


πŸŽ€ CONTRIBUTING

want in? yes bestie, come through ✨ you can send patches by email/via RT, or send pull requests on GitHub.

ngl tho β€” most of the time you do NOT need to build the distribution yourself. just edit the code, then test it with:
% prove -l

and if you do want to build the distribution (like, say, to try installing it locally on your system), then you'll want to install Dist::Zilla, Dist::Zilla::PluginBundle::Author::PERLANCAR, Pod::Weaver::PluginBundle::Author::PERLANCAR, and sometimes one or two other Dist::Zilla- and/or Pod::Weaver plugins. any extra steps you need beyond that? that's considered a bug, and you can report it to me πŸ›πŸ’€


this software is copyright (c) 2023, 2021, 2016, 2015, 2014, 2013 by perlancar <perlancar@cpan.org>.

it's free software πŸ¦‹ you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself. slay, open source, periodt.


πŸ› BUGS

found a bug? want a feature? pls pls pls report it on the bugtracker website: <https://rt.cpan.org/Public/Dist/Display.html?Name=Text-ANSI-Util> πŸ™ˆ

and when you submit a bug or a request, please include a test-file, or a patch to an existing test-file, that shows off the bug or the desired feature. that's the whole vibe check β€” a repro or it didn't happen, fr fr 😭


okay that's the whole page bestie, you made it to the end, main character energy πŸŽ‰ this was perl v5.36.0, dated 2023-06-11, and it's the Text::ANSI::Util(3pm) page β€” go forth and pad your strings with ANSI codes in them, you absolute legend πŸ’–βœ¨πŸŽ€

πŸ“¦ where this page comes from