Skip to content

Commit 1a404ff

Browse files
committed
Initial commit for phase 1 of Rich integration.
Still some work to do on help table formatting. Unit tests will be fixed after code review.
1 parent 9290ccf commit 1a404ff

File tree

6 files changed

+500
-332
lines changed

6 files changed

+500
-332
lines changed

cmd2/__init__.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,10 @@
1111
# package is not installed
1212
pass
1313

14-
from . import plugin
14+
from . import (
15+
plugin,
16+
rich_utils,
17+
)
1518
from .ansi import (
1619
Bg,
1720
Cursor,
@@ -101,6 +104,7 @@
101104
'SkipPostcommandHooks',
102105
# modules
103106
'plugin',
107+
'rich_utils',
104108
# Utilities
105109
'categorize',
106110
'CompletionMode',

cmd2/ansi.py

Lines changed: 5 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
setting the window title, and asynchronous alerts.
55
"""
66

7-
import functools
87
import re
98
from enum import (
109
Enum,
@@ -20,6 +19,8 @@
2019
wcswidth,
2120
)
2221

22+
from . import rich_utils
23+
2324
#######################################################
2425
# Common ANSI escape sequence constants
2526
#######################################################
@@ -29,38 +30,6 @@
2930
BEL = '\a'
3031

3132

32-
class AllowStyle(Enum):
33-
"""Values for ``cmd2.ansi.allow_style``"""
34-
35-
ALWAYS = 'Always' # Always output ANSI style sequences
36-
NEVER = 'Never' # Remove ANSI style sequences from all output
37-
TERMINAL = 'Terminal' # Remove ANSI style sequences if the output is not going to the terminal
38-
39-
def __str__(self) -> str:
40-
"""Return value instead of enum name for printing in cmd2's set command"""
41-
return str(self.value)
42-
43-
def __repr__(self) -> str:
44-
"""Return quoted value instead of enum description for printing in cmd2's set command"""
45-
return repr(self.value)
46-
47-
48-
# Controls when ANSI style sequences are allowed in output
49-
allow_style = AllowStyle.TERMINAL
50-
"""When using outside of a cmd2 app, set this variable to one of:
51-
52-
- ``AllowStyle.ALWAYS`` - always output ANSI style sequences
53-
- ``AllowStyle.NEVER`` - remove ANSI style sequences from all output
54-
- ``AllowStyle.TERMINAL`` - remove ANSI style sequences if the output is not going to the terminal
55-
56-
to control how ANSI style sequences are handled by ``style_aware_write()``.
57-
58-
``style_aware_write()`` is called by cmd2 methods like ``poutput()``, ``perror()``,
59-
``pwarning()``, etc.
60-
61-
The default is ``AllowStyle.TERMINAL``.
62-
"""
63-
6433
# Regular expression to match ANSI style sequence
6534
ANSI_STYLE_RE = re.compile(rf'{ESC}\[[^m]*m')
6635

@@ -135,7 +104,9 @@ def style_aware_write(fileobj: IO[str], msg: str) -> None:
135104
:param fileobj: the file object being written to
136105
:param msg: the string being written
137106
"""
138-
if allow_style == AllowStyle.NEVER or (allow_style == AllowStyle.TERMINAL and not fileobj.isatty()):
107+
if rich_utils.allow_style == rich_utils.AllowStyle.NEVER or (
108+
rich_utils.allow_style == rich_utils.AllowStyle.TERMINAL and not fileobj.isatty()
109+
):
139110
msg = strip_style(msg)
140111
fileobj.write(msg)
141112

@@ -1038,19 +1009,6 @@ def style(
10381009
return "".join(map(str, additions)) + str(value) + "".join(map(str, removals))
10391010

10401011

1041-
# Default styles for printing strings of various types.
1042-
# These can be altered to suit an application's needs and only need to be a
1043-
# function with the following structure: func(str) -> str
1044-
style_success = functools.partial(style, fg=Fg.GREEN)
1045-
"""Partial function supplying arguments to :meth:`cmd2.ansi.style()` which colors text to signify success"""
1046-
1047-
style_warning = functools.partial(style, fg=Fg.LIGHT_YELLOW)
1048-
"""Partial function supplying arguments to :meth:`cmd2.ansi.style()` which colors text to signify a warning"""
1049-
1050-
style_error = functools.partial(style, fg=Fg.LIGHT_RED)
1051-
"""Partial function supplying arguments to :meth:`cmd2.ansi.style()` which colors text to signify an error"""
1052-
1053-
10541012
def async_alert_str(*, terminal_columns: int, prompt: str, line: str, cursor_offset: int, alert_msg: str) -> str:
10551013
"""Calculate the desired string, including ANSI escape codes, for displaying an asynchronous alert message.
10561014

cmd2/argparse_custom.py

Lines changed: 30 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -246,7 +246,6 @@ def my_completer(self, text, line, begidx, endidx, arg_tokens)
246246
gettext,
247247
)
248248
from typing import (
249-
IO,
250249
TYPE_CHECKING,
251250
Any,
252251
Callable,
@@ -265,6 +264,8 @@ def my_completer(self, text, line, begidx, endidx, arg_tokens)
265264
Group,
266265
RenderableType,
267266
)
267+
from rich.table import Column, Table
268+
from rich.text import Text
268269
from rich_argparse import (
269270
ArgumentDefaultsRichHelpFormatter,
270271
MetavarTypeRichHelpFormatter,
@@ -274,8 +275,8 @@ def my_completer(self, text, line, begidx, endidx, arg_tokens)
274275
)
275276

276277
from . import (
277-
ansi,
278278
constants,
279+
rich_utils,
279280
)
280281

281282
if TYPE_CHECKING: # pragma: no cover
@@ -1053,10 +1054,17 @@ def _SubParsersAction_remove_parser(self: argparse._SubParsersAction, name: str)
10531054
class Cmd2HelpFormatter(RichHelpFormatter):
10541055
"""Custom help formatter to configure ordering of help text"""
10551056

1056-
# Render markup in usage, help, description, and epilog text.
1057-
RichHelpFormatter.usage_markup = True
1058-
RichHelpFormatter.help_markup = True
1059-
RichHelpFormatter.text_markup = True
1057+
# Disable automatic highlighting in the help text.
1058+
highlights = []
1059+
1060+
# Disable markup rendering in usage, help, description, and epilog text.
1061+
# cmd2's built-in commands do not escape opening brackets in their help text
1062+
# and therefore rely on these settings being False. If you desire to use
1063+
# markup in your help text, inherit from a Cmd2 help formatter and override
1064+
# these settings in that child class.
1065+
usage_markup = False
1066+
help_markup = False
1067+
text_markup = False
10601068

10611069
def _format_usage(
10621070
self,
@@ -1319,24 +1327,23 @@ def __init__(
13191327

13201328
def __rich__(self) -> Group:
13211329
"""Custom rendering logic."""
1322-
import rich
1323-
13241330
formatter = self.formatter_creator()
13251331

1326-
styled_title = rich.text.Text(
1332+
styled_title = Text(
13271333
type(formatter).group_name_formatter(f"{self.title}:"),
13281334
style=formatter.styles["argparse.groups"],
13291335
)
13301336

13311337
# Left pad the text like an argparse argument group does
13321338
left_padding = formatter._indent_increment
1333-
1334-
text_table = rich.table.Table(
1339+
text_table = Table(
1340+
Column(overflow="fold"),
13351341
box=None,
13361342
show_header=False,
13371343
padding=(0, 0, 0, left_padding),
13381344
)
13391345
text_table.add_row(self.text)
1346+
13401347
return Group(styled_title, text_table)
13411348

13421349

@@ -1413,12 +1420,20 @@ def error(self, message: str) -> NoReturn:
14131420
linum += 1
14141421

14151422
self.print_usage(sys.stderr)
1416-
formatted_message = ansi.style_error(formatted_message)
1417-
self.exit(2, f'{formatted_message}\n\n')
1423+
1424+
# Add failure style to message
1425+
console = self._get_formatter().console
1426+
with console.capture() as capture:
1427+
console.print(formatted_message, style=rich_utils.style_failure, crop=False)
1428+
formatted_message = f"{capture.get()}"
1429+
1430+
self.exit(2, f'{formatted_message}\n')
14181431

14191432
def _get_formatter(self) -> Cmd2HelpFormatter:
1420-
"""Copy of _get_formatter() with a different return type to assist type checkers."""
1421-
return cast(Cmd2HelpFormatter, super()._get_formatter())
1433+
"""Copy of _get_formatter() with customizations for Cmd2HelpFormatter."""
1434+
formatter = cast(Cmd2HelpFormatter, super()._get_formatter())
1435+
formatter.console = rich_utils.Cmd2Console(sys.stdout)
1436+
return formatter
14221437

14231438
def format_help(self) -> str:
14241439
"""Copy of format_help() from argparse.ArgumentParser with tweaks to separately display required parameters"""
@@ -1474,13 +1489,6 @@ def format_help(self) -> str:
14741489
# determine help from format above
14751490
return formatter.format_help() + '\n'
14761491

1477-
def _print_message(self, message: str, file: Optional[IO[str]] = None) -> None:
1478-
# Override _print_message to use style_aware_write() since we use ANSI escape characters to support color
1479-
if message:
1480-
if file is None:
1481-
file = sys.stderr
1482-
ansi.style_aware_write(file, message)
1483-
14841492
def create_text_group(self, title: str, text: RenderableType) -> TextGroup:
14851493
"""Create a TextGroup using this parser's formatter creator."""
14861494
return TextGroup(title, text, self._get_formatter)

0 commit comments

Comments
 (0)