Skip to content

Commit 0dbb4d2

Browse files
committed
Moved cmd2.Cmd._decolorized_write() to ansi.py and renamed it to ansi_aware_write().
1 parent 447479e commit 0dbb4d2

File tree

3 files changed

+36
-26
lines changed

3 files changed

+36
-26
lines changed

cmd2/ansi.py

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"""Support for ANSI escape sequences which are used for things like applying style to text"""
33
import functools
44
import re
5-
from typing import Any
5+
from typing import Any, IO
66

77
import colorama
88
from colorama import Fore, Back, Style
@@ -61,9 +61,14 @@
6161
'reset': Back.RESET,
6262
}
6363

64+
# ANSI escape sequences not provided by colorama
65+
UNDERLINE_ENABLE = colorama.ansi.code_to_chars(4)
66+
UNDERLINE_DISABLE = colorama.ansi.code_to_chars(24)
67+
6468

6569
def strip_ansi(text: str) -> str:
66-
"""Strip ANSI escape sequences from a string.
70+
"""
71+
Strip ANSI escape sequences from a string.
6772
6873
:param text: string which may contain ANSI escape sequences
6974
:return: the same string with any ANSI escape sequences removed
@@ -73,17 +78,25 @@ def strip_ansi(text: str) -> str:
7378

7479
def ansi_safe_wcswidth(text: str) -> int:
7580
"""
76-
Wraps wcswidth to make it compatible with colored strings
81+
Wrap wcswidth to make it compatible with strings that contains ANSI escape sequences
7782
7883
:param text: the string being measured
7984
"""
8085
# Strip ANSI escape sequences since they cause wcswidth to return -1
8186
return wcswidth(strip_ansi(text))
8287

8388

84-
# ANSI escape sequences not provided by colorama
85-
UNDERLINE_ENABLE = colorama.ansi.code_to_chars(4)
86-
UNDERLINE_DISABLE = colorama.ansi.code_to_chars(24)
89+
def ansi_aware_write(fileobj: IO, msg: str) -> None:
90+
"""
91+
Write a string to a fileobject and strip its ANSI escape sequences if required by allow_ansi setting
92+
93+
:param fileobj: the file object being written to
94+
:param msg: the string being written
95+
"""
96+
if allow_ansi.lower() == ANSI_NEVER.lower() or \
97+
(allow_ansi.lower() == ANSI_TERMINAL.lower() and not fileobj.isatty()):
98+
msg = strip_ansi(msg)
99+
fileobj.write(msg)
87100

88101

89102
def style(text: Any, *, fg: str = '', bg: str = '', bold: bool = False, underline: bool = False) -> str:

cmd2/argparse_completer.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ def my_completer(text: str, line: str, begidx: int, endidx:int, extra_param: str
6666
from argparse import ZERO_OR_MORE, ONE_OR_MORE, ArgumentError, _, _get_action_name, SUPPRESS
6767
from typing import List, Dict, Tuple, Callable, Union
6868

69-
from .ansi import ansi_safe_wcswidth, style_error
69+
from .ansi import ansi_aware_write, ansi_safe_wcswidth, style_error
7070
from .rl_utils import rl_force_redisplay
7171

7272
# attribute that can optionally added to an argparse argument (called an Action) to
@@ -1047,6 +1047,13 @@ def format_help(self) -> str:
10471047
# determine help from format above
10481048
return formatter.format_help() + '\n'
10491049

1050+
def _print_message(self, message, file=None):
1051+
# Override _print_message to use ansi_aware_write() since we use ANSI escape characters to support color
1052+
if message:
1053+
if file is None:
1054+
file = _sys.stderr
1055+
ansi_aware_write(file, message)
1056+
10501057
def _get_nargs_pattern(self, action) -> str:
10511058
# Override _get_nargs_pattern behavior to use the nargs ranges provided by AutoCompleter
10521059
if isinstance(action, _RangeAction) and \

cmd2/cmd2.py

Lines changed: 9 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@
4040
import threading
4141
from collections import namedtuple
4242
from contextlib import redirect_stdout
43-
from typing import Any, Callable, Dict, List, Mapping, Optional, Tuple, Type, Union, IO
43+
from typing import Any, Callable, Dict, List, Mapping, Optional, Tuple, Type, Union
4444

4545
import colorama
4646

@@ -589,16 +589,6 @@ def allow_redirection(self, value: bool) -> None:
589589
"""Setter for the allow_redirection property that determines whether or not redirection of stdout is allowed."""
590590
self._statement_parser.allow_redirection = value
591591

592-
def _decolorized_write(self, fileobj: IO, msg: str) -> None:
593-
"""Write a string to a fileobject, stripping ANSI escape sequences if necessary
594-
595-
Honor the current colors setting, which requires us to check whether the fileobject is a tty.
596-
"""
597-
if ansi.allow_ansi.lower() == ansi.ANSI_NEVER.lower() or \
598-
(ansi.allow_ansi.lower() == ansi.ANSI_TERMINAL.lower() and not fileobj.isatty()):
599-
msg = ansi.strip_ansi(msg)
600-
fileobj.write(msg)
601-
602592
def poutput(self, msg: Any, *, end: str = '\n') -> None:
603593
"""Print message to self.stdout and appends a newline by default
604594
@@ -610,7 +600,7 @@ def poutput(self, msg: Any, *, end: str = '\n') -> None:
610600
:param end: string appended after the end of the message, default a newline
611601
"""
612602
try:
613-
self._decolorized_write(self.stdout, "{}{}".format(msg, end))
603+
ansi.ansi_aware_write(self.stdout, "{}{}".format(msg, end))
614604
except BrokenPipeError:
615605
# This occurs if a command's output is being piped to another
616606
# process and that process closes before the command is
@@ -632,7 +622,7 @@ def perror(self, msg: Any, *, end: str = '\n', apply_style: bool = True) -> None
632622
final_msg = ansi.style_error(msg)
633623
else:
634624
final_msg = "{}".format(msg)
635-
self._decolorized_write(sys.stderr, final_msg + end)
625+
ansi.ansi_aware_write(sys.stderr, final_msg + end)
636626

637627
def pexcept(self, msg: Any, *, end: str = '\n', apply_style: bool = True) -> None:
638628
"""Print Exception message to sys.stderr. If debug is true, print exception traceback if one exists.
@@ -668,7 +658,7 @@ def pfeedback(self, msg: str) -> None:
668658
if self.feedback_to_output:
669659
self.poutput(msg)
670660
else:
671-
self._decolorized_write(sys.stderr, "{}\n".format(msg))
661+
ansi.ansi_aware_write(sys.stderr, "{}\n".format(msg))
672662

673663
def ppaged(self, msg: str, end: str = '\n', chop: bool = False) -> None:
674664
"""Print output using a pager if it would go off screen and stdout isn't currently being redirected.
@@ -717,7 +707,7 @@ def ppaged(self, msg: str, end: str = '\n', chop: bool = False) -> None:
717707
pipe_proc = subprocess.Popen(pager, shell=True, stdin=subprocess.PIPE)
718708
pipe_proc.communicate(msg_str.encode('utf-8', 'replace'))
719709
else:
720-
self._decolorized_write(self.stdout, msg_str)
710+
ansi.ansi_aware_write(self.stdout, msg_str)
721711
except BrokenPipeError:
722712
# This occurs if a command's output is being piped to another process and that process closes before the
723713
# command is finished. If you would like your application to print a warning message, then set the
@@ -2162,7 +2152,7 @@ def default(self, statement: Statement) -> Optional[bool]:
21622152
return self.do_shell(statement.command_and_args)
21632153
else:
21642154
err_msg = self.default_error.format(statement.command)
2165-
self._decolorized_write(sys.stderr, "{}\n".format(err_msg))
2155+
ansi.ansi_aware_write(sys.stderr, "{}\n".format(err_msg))
21662156

21672157
def _pseudo_raw_input(self, prompt: str) -> str:
21682158
"""Began life as a copy of cmd's cmdloop; like raw_input but
@@ -2695,7 +2685,7 @@ def do_help(self, args: argparse.Namespace) -> None:
26952685
# If there is no help information then print an error
26962686
elif help_func is None and (func is None or not func.__doc__):
26972687
err_msg = self.help_error.format(args.command)
2698-
self._decolorized_write(sys.stderr, "{}\n".format(err_msg))
2688+
ansi.ansi_aware_write(sys.stderr, "{}\n".format(err_msg))
26992689

27002690
# Otherwise delegate to cmd base class do_help()
27012691
else:
@@ -3767,7 +3757,7 @@ class TestMyAppCase(Cmd2TestCase):
37673757
test_results = runner.run(testcase)
37683758
execution_time = time.time() - start_time
37693759
if test_results.wasSuccessful():
3770-
self._decolorized_write(sys.stderr, stream.read())
3760+
ansi.ansi_aware_write(sys.stderr, stream.read())
37713761
finish_msg = '{0} transcript{1} passed in {2:.3f} seconds'.format(num_transcripts, plural, execution_time)
37723762
finish_msg = ansi.style_success(utils.center_text(finish_msg, pad='='))
37733763
self.poutput(finish_msg)
@@ -4026,7 +4016,7 @@ def _report_disabled_command_usage(self, *args, message_to_print: str, **kwargs)
40264016
:param message_to_print: the message reporting that the command is disabled
40274017
:param kwargs: not used
40284018
"""
4029-
self._decolorized_write(sys.stderr, "{}\n".format(message_to_print))
4019+
ansi.ansi_aware_write(sys.stderr, "{}\n".format(message_to_print))
40304020

40314021
def cmdloop(self, intro: Optional[str] = None) -> int:
40324022
"""This is an outer wrapper around _cmdloop() which deals with extra features provided by cmd2.

0 commit comments

Comments
 (0)