2

Is it possible to interpret console codes for colouring with minted?

My current approach:

Create a file containing console codes with

$ printf "normal text\n\033[0;36mcyan text\033[0;0m\nnormal text" > output.log

Consider the following minimal example,

\documentclass{article}
\usepackage[utf8]{inputenc}
\usepackage{minted}

\begin{document} \inputminted{bc}{output.log} \end{document}

and compile it with:

$ pdflatex --shell-escape example.tex

The error I get:

...
(/usr/share/texmf-dist/tex/latex/graphics-def/pdftex.def))/usr/bin/pygmentize

(/usr/share/texmf-dist/tex/latex/l3backend/l3backend-pdfmode.def) No file example.aux. (/usr/share/texmf-dist/tex/context/base/mkii/supp-pdf.mkii [Loading MPS to PDF converter (version 2006.09.02).] ) (./_minted-example/default-pyg-prefix.pygstyle) (./_minted-example/default.pygstyle) (./_minted-example/D4C350DCEFCF48E3B9E6D0F4E55D3D0403FA95147BD0676DCEE8C50F74C9 315E.pygtex

! Package inputenc Error: Unicode character ^^[ (U+001B) (inputenc) not set up for use with LaTeX.

See the inputenc package documentation for explanation. Type H <return> for immediate help. ...

l.3 ...text^^[[\PYG{l+m}{0}\PYG{p}{;}\PYG{l+m}{0}m

?

Currently the file output.log cannot be read, because the inputenc package does not know how to handle the control character at the beginning of the console code. How can this issue be addressed? Once this is done, is it possible to let minted interpret the console codes for colouring (i.e. print the cyan coloured text in the example above actually in cyan)?

user11718766
  • 123
  • 2
  • Very related, maybe possible duplicate: https://tex.stackexchange.com/questions/511880/insert-text-with-terminal-color-escape-sequences. – Marijn Mar 09 '21 at 09:47
  • Maybe also https://tex.stackexchange.com/a/307265/ would work, although that answer does not specifically mention ANSI color codes. – Marijn Mar 09 '21 at 09:54
  • https://tex.stackexchange.com/a/512300/216364 does look very promising, unfortunately some words appear multiple times and at incorrect positions. – user11718766 Mar 09 '21 at 10:07
  • https://tex.stackexchange.com/a/511945/216364 looks great as well, but when I want to \input a file I run into the same problem with inputenc as described above. – user11718766 Mar 09 '21 at 10:08

1 Answers1

2

The approach in https://tex.stackexchange.com/a/511945/ can be extended to work with an external file as well (as discussed in the comments there, but not incorporated in the answer). The issue with the external file is that the color command in that file is stored using the single control character \033 (001B in hexadecimal) instead of a literal \e (i.e., a \ followed by an e) like in the example with inline code. You can however map this character to a LaTeX command using \DeclareUnicodeCharacter.

MWE:

\documentclass{article}
\usepackage[utf8]{inputenc}
\DeclareUnicodeCharacter{001B}{\ansi}
\usepackage{fancyvrb}
\usepackage{color}

\def\defaultcode{[0;0} \def\bluecode{[0;34} \def\redcode{[0;31} \def\cyancode{[0;36} \def\ansi#1m{%
\def\colcode{#1}%
\ifx\colcode\defaultcode\color{black}% \else\ifx\colcode\bluecode\color{blue}% \else\ifx\colcode\redcode\color{red}% \else\ifx\colcode\cyancode\color{cyan}% \fi\fi\fi\fi} \begin{document} \VerbatimInput[commandchars=\{}]{clroutput.log} \end{document}

Result:

enter image description here


An alternative approach is to use minted with a custom lexer and style file for ANSI colors. The comments in the other question mention the lexer https://github.com/chriskuehl/pygments-ansi-color. This lexer was developed with HTML output in mind, so for use in minted some extra code is needed.

The lexer itself (pygments_ansi_color.py) can be used without changes and can be placed in the same directory as the document. Additionally a style file must be placed in the pygments/styles system directory. The filename of the style file is important because it needs to correspond to the style class within the file. For the MWE below the file should be called ansicolor.py, with the following content:

from pygments.style import Style
from pygments.token import Keyword, Name, Comment, String, Error, \
     Number, Operator, Literal, Text, Generic, Whitespace, Token
import itertools

C = Token.C Color = Token.Color

def _token_from_lexer_state(bold, faint, fg_color, bg_color): """Construct a token given the current lexer state.

We can only emit one token even though we have a multiple-tuple state.
To do work around this, we construct tokens like &quot;Bold.Red&quot;.
&quot;&quot;&quot;
components = ()

if bold:
    components += ('Bold',)

if faint:
    components += ('Faint',)

if fg_color:
    components += (fg_color,)

if bg_color:
    components += ('BG' + bg_color,)

if len(components) == 0:
    return Text
else:
    token = Color
    for component in components:
        token = getattr(token, component)
    return token

def color_tokens(fg_colors, bg_colors, enable_256color=False): styles = {}

if enable_256color:
    styles[Token.C.Bold] = 'bold'
    styles[Token.C.Faint] = ''
    for i, color in _256_colors.items():
        styles[getattr(Token.C, 'C{}'.format(i))] = color
        styles[getattr(Token.C, 'BGC{}'.format(i))] = 'bg:{}'.format(color)

    for color, value in fg_colors.items():
        styles[getattr(C, color)] = value

    for color, value in bg_colors.items():
        styles[getattr(C, 'BG{}'.format(color))] = 'bg:{}'.format(value)
else:
    for bold, faint, fg_color, bg_color in itertools.product(
            (False, True),
            (False, True),
            {None} | set(fg_colors),
            {None} | set(bg_colors),
    ):
        token = _token_from_lexer_state(bold, faint, fg_color, bg_color)
        if token is not Text:
            value = []
            if bold:
                value.append('bold')
            if fg_color:
                value.append(fg_colors[fg_color])
            if bg_color:
                value.append('bg:' + bg_colors[bg_color])
            styles[token] = ' '.join(value)

return styles

Note: You can use different background colors for improved readability.

fg_colors = bg_colors = { 'Black': '#000000', 'Red': '#EF2929', 'Green': '#8AE234', 'Yellow': '#FCE94F', 'Blue': '#3465A4', 'Magenta': '#c509c5', 'Cyan': '#34E2E2', 'White': '#ffffff', } class AnsicolorStyle(Style): styles = default_style = ''

styles = {
    Comment:                '#177500',
    Comment.Preproc:        '#633820',

    String:                 '#C41A16',
    String.Char:            '#2300CE',

    Operator:               '#000000',

    Keyword:                '#A90D91',

    Name:                   '#000000',
    Name.Attribute:         '#836C28',
    Name.Class:             '#3F6E75',
    Name.Function:          '#000000',
    Name.Builtin:           '#A90D91',
    Name.Builtin.Pseudo:    '#5B269A',
    Name.Variable:          '#000000',
    Name.Tag:               '#000000',
    Name.Decorator:         '#000000',
    Name.Label:             '#000000',

    Literal:                '#1C01CE',
    Number:                 '#1C01CE',
    Error:                  '#000000',
}
styles.update(color_tokens(fg_colors, bg_colors))

Then the following MWE will produce colored output for the escape codes:

\documentclass{article}
\usepackage{minted}
\usemintedstyle{ansicolor} % set the style file

\begin{document} % call minted with the ANSI lexer \inputminted{pygments_ansi_color.py:AnsiColorLexer -x}{clroutput.log} \end{document}

Result compared to the approach above (for a file with a cyan and a blue line):

enter image description here

Note that the minted output has slightly different colors. You can customize those by changing the values in the style file above. Note that you need to delete the cached results from minted every time you change the style file in order to see the results.

Note also that the style file contains many categories that are not actually used. The style is copied from xcode.py, following the documentation of pygments-ansi-color.

Marijn
  • 37,699