Search code examples
windowsperlcmdcolorswindows-console

Coloring Perl output on windows command prompt


This question is related to the question: How do I color output text from Perl script on Windows?

But is a bit more specific. I have gotten cross-platform colorization working, to some extent:

use Term::ANSIColor;
use Win32::Console;

if (!(-f STDOUT)) {
    if ($^O =~ /win/) {
        our $FG_BLUE;
        our $FG_YELLOW;
        our $FG_RED;
        our $BG_GREEN;
        my $CONSOLE = Win32::Console->new(STD_OUTPUT_HANDLE);
        my $attr = $CONSOLE->Attr(); # Get current console colors
        $blue   = sub {$CONSOLE->Attr($FG_BLUE);return};
        $reset  = sub {$CONSOLE->Attr($attr);return};
        $yellow = sub {$CONSOLE->Attr($FG_YELLOW);return};
        $red    = sub {$CONSOLE->Attr($FG_RED);return};
    } else {
        $blue   = sub {return color('bold blue')};
        $reset  = sub {return color('reset')};
        $yellow = sub {return color('yellow')};
        $red    = sub {return color('red')};
    }
}

but The Terminal colors do not change immediately when the functions are called from inside strings, thus:

    print "${\$blue->()} this is blue\n";
    print "${\$blue->()}This is... not blue${\$reset->()}\n";
    print "this is Blue ${\$blue->()}\n";
    print "this is reset${\$reset->()}\n";

I am wondering if it is possible to do things such as:

    my $print_help = <<PRINT_HELP;
    Usage:  $toolname [-Options] [-fields name1,[name2],...]
    ${\$red->()} toolname version VERSION ${\$reset->()} 
    ${\$blue->()} options: ${\$reset->()}

    PRINT_HELP

    print $print_help;

prints with no colors. I have tried setting $| = 1 with no luck.

I do not have the option to install Win32::Console::ANSI on the system in question, so I am not able to make any solutions that use that module work.


Solution

  • You are calling red, reset, blue and reset before you even start printing. You could use a template. Here's a robust implementation:

    use FindBin qw( $RealBin );
    use lib "$RealBin/lib";
    
    use My::Console qw( );
    
    my $console = My::Console->new;
    
    my $print_help = <<'__END_OF_HELP__';
    Usage:  $toolname [-Options] [-fields name1,[name2],...]
    {{red}}toolname version VERSION{{reset}}
    {{blue}}options:{{reset}}
    
    __END_OF_HELP__
    
    $console->print_with_color($print_help);
    

    lib/My/Console.pm:

    package My::Console;
    
    use strict;
    use warnings;
    
    my $console;
    BEGIN {
       if (!-t STDOUT) {
          require My::Console::Dumb;
          $console = My::Console::Dumb::;
       }
       elsif ($^O eq 'Win32') {
          require My::Console::Win32;
          $console = My::Console::Win32::;
       }
       else {
          require My::Console::ANSI;
          $console = My::Console::ANSI::;
       }
    }
    
    sub new { $console }
    
    1;
    

    lib/My/Console/Base.pm:

    package My::Console::Base;
    
    use strict;
    use warnings;
    
    use Carp qw( croak );
    
    my %allowed_cmds = map { $_ => 1 } qw( red blue reset );
    
    sub red   { }
    sub blue  { }
    sub reset { }
    
    sub print { print(STDOUT @_); }
    
    sub print_with_color {
       my $self = shift;
    
       for (@_) {
          /\G ( (?: [^{] | \{(?!\{) )+ ) /xgc
             and $self->print($1);
    
          /\G \z /xgc
             and next;
    
          /\G \{\{ /xgc;
    
          /\G ( (?: [^}] | \}(?!\}) )* ) \}\} /xgc
             or croak("Bad template");
    
          my $cmd = $1;
          if ($cmd eq "") {
             # An escape mechanism. Use "{{}}" to output "{{".
             $self->print("{{");
             redo;
          }
    
          $allowed_cmds{$cmd}
             or croak("Unrecognized command \"$cmd\"");
    
          $self->$cmd();
          redo;
       }
    }
    
    1;
    

    lib/My/Console/Win32.pm:

    package My::Console::Win32;
    
    use strict;
    use warnings;
    
    use My::Console::Base qw( );
    use Win32::Console;
    
    our @ISA = My::Console::Base::;
    
    my $CONSOLE = Win32::Console->new(STD_OUTPUT_HANDLE);
    my $initial_console_attr = $CONSOLE->Attr();
    
    sub red   { STDOUT->flush; $CONSOLE->Attr($FG_RED); }
    sub blue  { STDOUT->flush; $CONSOLE->Attr($FG_BLUE); }
    sub reset { STDOUT->flush; $CONSOLE->Attr($initial_console_attr); }
    
    1;
    

    lib/My/Console/ANSI.pm:

    package My::Console::ANSI;
    
    use strict;
    use warnings;
    
    use My::Console::Base qw( );
    use Term::ANSIColor   qw( );
    
    our @ISA = My::Console::Base::;
    
    sub red   { print(Term::ANSIColor::red()); }
    sub blue  { print(Term::ANSIColor::blue()); }
    sub reset { print(Term::ANSIColor::reset()); }
    
    1;
    

    lib/My/Console/Dumb.pm:

    package My::Console::Dumb;
    
    use strict;
    use warnings;
    
    use My::Console::Base qw( );
    
    our @ISA = My::Console::Base::;
    
    1;