3232
3333 .. moduleauthor:: Sebastian Wiesner <lunaryorn@gmail.com>
3434"""
35-
36- from __future__ import print_function , division , absolute_import
37-
38- import sys
3935import os
36+ import re
4037import shlex
41- from subprocess import Popen , PIPE , STDOUT
42- from collections import defaultdict , namedtuple
38+ import sys
39+ from collections import defaultdict
40+ from collections import namedtuple
41+ from subprocess import PIPE
42+ from subprocess import STDOUT
43+ from subprocess import Popen
4344
4445from docutils import nodes
4546from docutils .parsers import rst
46- from docutils .parsers .rst .directives import flag , unchanged , nonnegative_int
47+ from docutils .parsers .rst .directives import flag
48+ from docutils .parsers .rst .directives import nonnegative_int
49+ from docutils .parsers .rst .directives import unchanged
4750from docutils .statemachine import StringList
48-
4951from sphinx .util import logging as sphinx_logging
5052
5153__version__ = '0.19.dev0'
@@ -84,6 +86,39 @@ def _slice(value):
8486 return tuple ((parts + [None ] * 2 )[:2 ])
8587
8688
89+ _ANSI_FORMAT_SEQUENCE = re .compile (r'\x1b\[[^m]+m' )
90+
91+
92+ def _strip_ansi_formatting (text ):
93+ return _ANSI_FORMAT_SEQUENCE .sub ('' , text )
94+
95+
96+ def _create_output_node (output , use_ansi , app = None ):
97+ if not use_ansi :
98+ return nodes .literal_block (output , output )
99+
100+ if app is not None and 'erbsland.sphinx.ansi' not in app .extensions :
101+ logger .warning (
102+ "programoutput_use_ansi is enabled, but 'erbsland.sphinx.ansi' "
103+ "is not enabled. Stripping ANSI escape codes instead."
104+ )
105+ stripped_output = _strip_ansi_formatting (output )
106+ return nodes .literal_block (stripped_output , stripped_output )
107+
108+ try :
109+ from erbsland .sphinx .ansi .parser import ANSILiteralBlock
110+ except ImportError : # pragma: no cover
111+ logger .warning (
112+ "programoutput_use_ansi is enabled, but erbsland ANSI support is "
113+ "not available. Stripping ANSI escape codes instead. Install "
114+ "'erbsland-sphinx-ansi' and enable 'erbsland.sphinx.ansi' to "
115+ "render ANSI output."
116+ )
117+ stripped_output = _strip_ansi_formatting (output )
118+ return nodes .literal_block (stripped_output , stripped_output )
119+ return ANSILiteralBlock (output , output )
120+
121+
87122class ProgramOutputDirective (rst .Directive ):
88123 has_content = False
89124 final_argument_whitespace = True
@@ -308,13 +343,9 @@ def run_programs(app, doctree):
308343 returncode = returncode
309344 )
310345
311- # The node_class used to be switchable to
312- # `sphinxcontrib.ansi.ansi_literal_block` if
313- # `app.config.programoutput_use_ansi` was set. But
314- # sphinxcontrib.ansi is no longer available on PyPI, so we
315- # can't test that. And if we can't test it, we can't
316- # support it.
317- new_node = nodes .literal_block (output , output )
346+ new_node = _create_output_node (
347+ output , app .config .programoutput_use_ansi , app
348+ )
318349 new_node ['language' ] = node ['language' ]
319350 node .replace_self (new_node )
320351
@@ -335,6 +366,7 @@ def init_cache(app):
335366def setup (app ):
336367 app .add_config_value ('programoutput_prompt_template' ,
337368 '$ {command}\n {output}' , 'env' )
369+ app .add_config_value ('programoutput_use_ansi' , False , 'env' )
338370 app .add_directive ('program-output' , ProgramOutputDirective )
339371 app .add_directive ('command-output' , ProgramOutputDirective )
340372 app .connect ('builder-inited' , init_cache )
0 commit comments