fireblood has asked for the wisdom of the Perl Monks concerning the following question:

Dear wise ones,

I have created a utility written in Perl for use by members of my department. If it is invoked with a parameter of "-h" it writes help information to the screen. I have near the top of the script

   use Term::ANSIColor qw (color colored colorstrip :constants);

which allows me to color keywords in the help text that is generated. When I display the help text, if I use the regular print function, strings like

   "The purpose of the " . colored ("ts", "green") . " script is to ..."

work perfectly. However, I just tried using a format and the write instruction to generate the same information with better formatting, and found that the ANSII codes that are properly interpreted and rendered by the print instruction are being displayed by the write function as text, e.g.

The purpose of the [32mts [0m script is to search for a ...

Is there a way to use the write function and a format to display text with embedded ANSII codes being interpreted as control characters rather than as text?

Thank you.

Replies are listed 'Best First'.
Re: Is it possible to include colored text in formatted output using write
by 1nickt (Canon) on May 11, 2023 at 22:46 UTC

    Hi, from the doc for Term::ANSIColor:

    It's not possible to use this module to embed formatting and color attributes using Perl formats. They replace the escape character with a space (as documented in perlform(1)), resulting in garbled output from the unrecognized attribute. Even if there were a way around that problem, the format doesn't know that the non-printing escape sequence is zero-length and would incorrectly format the output. For formatted output using color or other attributes, either use sprintf() instead or use formline() and then add the color or other attributes after formatting and before output.

    Hope this helps!


    The way forward always starts with a minimal test.

      Hi, thank you, this is what I needed to know. I'll now be able to stop needlessly chasing down this path.

      Thanks much and have a great day!

Re: Is it possible to include colored text in formatted output using write
by tybalt89 (Monsignor) on May 11, 2023 at 23:24 UTC

      That's a very clever way to approach this. Thanks for the reference!

Re: Is it possible to include colored text in formatted output using write
by bliako (Abbot) on May 11, 2023 at 22:19 UTC

    can you provide a Short, Self-Contained, Correct Example to show how you use format? I am uninitiated.

    print with sprintf works fine for me:

    use Term::ANSIColor qw (color colored colorstrip :constants); my $x = sprintf '%s XYZ', "Hello ".colored("there", "green")." and bye +."; print $x; print sprintf("123 %s 456 %s 789\n", colored("hello", "red").sprintf("abc %s xyz", colored("bye", "green") ), $x );

    Dirty hack: re-escaping refreshing the ANSI escape sequence in The purpose of the [32mts [0m script... works for me with (the naive) =~ s/\[/\033[/g;

    bw, bliako

      Hi Bliako, this is what I used to repro the issue:

      use strict; use warnings; use Term::ANSIColor qw (color colored colorstrip :constants); my $str = "The purpose of the " . colored ("ts", "green") . " script i +s to ..."; print "$str\n"; format STDOUT = @<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< $str . write;


      The way forward always starts with a minimal test.

      Here is what I currently have. I'm defining the format in an eval instruction because later I plan to set the length of the field based on the width of the screen window, but at present it is as follows:

      my $format_definition_expression = "format one_field_format =\n" . "@<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n" . "\$one_field_text\n" . ".\n"; eval $format_definition_expression;

      Later on down I have:

      unless (open ($fh_more_pipe, "|-", "cat - | more")) { print "\nCould not open \$fh_more_pipe: $!\n\n"; die; } select $fh_more_pipe; $|++; $~ = "one_field_format"; my $one_field_text = "The purpose of the " . colored ($StdHdr::ThisScriptName, "green") . " script is to search for a specified character string " . "in all plain text files that reside in and below a " . "specified starting directory, optionally writing " . "the names of matching files to a specified save file." . "n\n"; write;

      I know that I will need to add double-tildes (~~) to the line in the format definition to make the text that I want to display to flow to as many subsequent lines as necessary to accommodate the entire text of the message, but for now I'm just focusing on getting the colored text elements to be properly rendered in just the first line before going on to that.

      Thanks for looking at this.

Re: Is it possible to include colored text in formatted output using write
by kcott (Archbishop) on May 13, 2023 at 03:20 UTC

    G'day fireblood,

    As ++1nickt has pointed out, you can't embed colour attributes in Perl formats. That documentation extract suggests formline; however, that has a problem with the ~~ you wanted to use.

    I believe the following code achieves what you're after. I strongly recommend writing short routines to handle the colouring: a lot less typing in your help text and changing a colour only requires modification in one place. I had a bit of a play around with splitting lines; I've shown some examples with hard- and soft-hyphens; there are probably enough ideas to see what's possible; I'll leave you to adapt this to your needs.

    [Unicode info: "\x{1b}" eq "\N{ESCAPE}" and "\x{ad}" eq "\N{SOFT HYPHEN}"]

    #!/usr/bin/env perl use strict; use warnings; use constant BASE_LINE_LENGTH => 40; use Term::ANSIColor 'colored'; my $base_line_length = @ARGV ? $ARGV[0] : BASE_LINE_LENGTH; my @strings = ( 'A message indicating success might look' . " some\N{SOFT HYPHEN}thing like this: " . _good("OK: some\N{SOFT HYPHEN}thing worked.") . ' That is, it should be green.', 'A message indicating failure might look some-thing like this: ' . _bad("Error: something didn't work.") . ' That is, it should be yellow on red.', ); my $off_code = "\x{1b}[0m"; my $last_code = $off_code; for my $string (@strings) { my $char_count = 0; for my $substr (split /(\x{1b}\[[0-9;]+m)/, $string) { if ($substr =~ /^\x{1b}/) { $last_code = $substr; } else { print $last_code; CHAR: for my $char (split //, $substr) { if ($char_count == 0) { print $last_code; } if ($char_count > $base_line_length && $char =~ /[\x{a +d} -]/) { print '-' unless $char eq ' '; print "$off_code\n"; $char_count = 0; next CHAR; } unless ($char eq "\x{ad}") { print $char; ++$char_count; } } } } print "$off_code\n"; } sub _good { my ($string) = @_; return colored($string, 'green'); } sub _bad { my ($string) = @_; return colored($string, 'yellow on_red'); }

    In the following, italicised text indicates "green"; emboldened text indicates "yellow on_red".

    Example output with default base line length:

    $ ./colour_format.pl
    A message indicating success might look some-
    thing like this: OK: something worked. That
    is, it should be green.
    A message indicating failure might look some-
    thing like this: Error: something didn't work.
    That is, it should be yellow on red.
    

    Example output with user-supplied base line length:

    $ ./colour_format.pl 32
    A message indicating success might
    look something like this: OK: some-
    thing worked. That is, it should be
    green.
    A message indicating failure might
    look some-thing like this: Error:
    something didn't work. That is, it
    should be yellow on red.
    

    — Ken