Skip to content

Commit 100ed9f

Browse files
committed
Reorganized a bit to move towards separation between where to print from what and how to print
1 parent 7ee398b commit 100ed9f

File tree

2 files changed

+130
-56
lines changed

2 files changed

+130
-56
lines changed

cmd2/argparse_custom.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1118,7 +1118,7 @@ def _format_usage(
11181118

11191119
# build full usage string
11201120
format = self._format_actions_usage
1121-
action_usage = format(required_options + optionals + positionals, groups)
1121+
action_usage = format(required_options + optionals + positionals, groups) # type: ignore[arg-type]
11221122
usage = ' '.join([s for s in [prog, action_usage] if s])
11231123

11241124
# wrap the usage parts if it's too long
@@ -1128,9 +1128,9 @@ def _format_usage(
11281128

11291129
# break usage into wrappable parts
11301130
part_regexp = r'\(.*?\)+|\[.*?\]+|\S+'
1131-
req_usage = format(required_options, groups)
1132-
opt_usage = format(optionals, groups)
1133-
pos_usage = format(positionals, groups)
1131+
req_usage = format(required_options, groups) # type: ignore[arg-type]
1132+
opt_usage = format(optionals, groups) # type: ignore[arg-type]
1133+
pos_usage = format(positionals, groups) # type: ignore[arg-type]
11341134
req_parts = re.findall(part_regexp, req_usage)
11351135
opt_parts = re.findall(part_regexp, opt_usage)
11361136
pos_parts = re.findall(part_regexp, pos_usage)

cmd2/cmd2.py

Lines changed: 126 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@
5454
ModuleType,
5555
)
5656
from typing import (
57+
IO,
5758
Any,
5859
Callable,
5960
Dict,
@@ -601,11 +602,14 @@ def register_command_set(self, cmdset: CommandSet) -> None:
601602
raise CommandSetRegistrationError(f'Duplicate settable {key} is already registered')
602603

603604
cmdset.on_register(self)
604-
methods = inspect.getmembers(
605-
cmdset,
606-
predicate=lambda meth: isinstance(meth, Callable) # type: ignore[arg-type]
607-
and hasattr(meth, '__name__')
608-
and meth.__name__.startswith(COMMAND_FUNC_PREFIX),
605+
methods = cast(
606+
List[Tuple[str, Callable[..., Any]]],
607+
inspect.getmembers(
608+
cmdset,
609+
predicate=lambda meth: isinstance(meth, Callable) # type: ignore[arg-type]
610+
and hasattr(meth, '__name__')
611+
and meth.__name__.startswith(COMMAND_FUNC_PREFIX),
612+
),
609613
)
610614

611615
default_category = getattr(cmdset, CLASS_ATTR_DEFAULT_HELP_CATEGORY, None)
@@ -1056,7 +1060,40 @@ def visible_prompt(self) -> str:
10561060
"""
10571061
return ansi.strip_style(self.prompt)
10581062

1059-
def poutput(self, msg: Any = '', *, end: str = '\n', apply_style: bool = True) -> None:
1063+
def print_to(
1064+
self,
1065+
dest: Union[TextIO, IO[str]],
1066+
msg: Any,
1067+
*,
1068+
end: str = '\n',
1069+
style: Optional[Callable[[str], str]] = None,
1070+
paged: bool = False,
1071+
chop: bool = False,
1072+
) -> None:
1073+
final_msg = style(msg) if style is not None else msg
1074+
if paged:
1075+
self.ppaged(final_msg, end=end, chop=chop, dest=dest)
1076+
else:
1077+
try:
1078+
ansi.style_aware_write(dest, f'{final_msg}{end}')
1079+
except BrokenPipeError:
1080+
# This occurs if a command's output is being piped to another
1081+
# process and that process closes before the command is
1082+
# finished. If you would like your application to print a
1083+
# warning message, then set the broken_pipe_warning attribute
1084+
# to the message you want printed.
1085+
if self.broken_pipe_warning:
1086+
sys.stderr.write(self.broken_pipe_warning)
1087+
1088+
def poutput(
1089+
self,
1090+
msg: Any = '',
1091+
*,
1092+
end: str = '\n',
1093+
apply_style: bool = True,
1094+
paged: bool = False,
1095+
chop: bool = False,
1096+
) -> None:
10601097
"""Print message to self.stdout and appends a newline by default
10611098
10621099
Also handles BrokenPipeError exceptions for when a command's output has
@@ -1067,62 +1104,85 @@ def poutput(self, msg: Any = '', *, end: str = '\n', apply_style: bool = True) -
10671104
:param end: string appended after the end of the message, default a newline
10681105
:param apply_style: If True, then ansi.style_output will be applied to the message text. Set to False in cases
10691106
where the message text already has the desired style. Defaults to True.
1107+
:param paged: If True, pass the output through the configured pager.
1108+
:param chop: If paged is True, True to truncate long lines or False to wrap long lines.
10701109
"""
1071-
if apply_style:
1072-
final_msg = ansi.style_output(msg)
1073-
else:
1074-
final_msg = str(msg)
1075-
try:
1076-
ansi.style_aware_write(self.stdout, f"{final_msg}{end}")
1077-
except BrokenPipeError:
1078-
# This occurs if a command's output is being piped to another
1079-
# process and that process closes before the command is
1080-
# finished. If you would like your application to print a
1081-
# warning message, then set the broken_pipe_warning attribute
1082-
# to the message you want printed.
1083-
if self.broken_pipe_warning:
1084-
sys.stderr.write(self.broken_pipe_warning)
1110+
self.print_to(self.stdout, msg, end=end, style=ansi.style_output if apply_style else None, paged=paged, chop=chop)
10851111

1086-
def psuccess(self, msg: Any = '', *, end: str = '\n', apply_style: bool = True) -> None:
1087-
"""Wraps poutput but applies ansi.style_success by default
1112+
# noinspection PyMethodMayBeStatic
1113+
def perror(
1114+
self,
1115+
msg: Any = '',
1116+
*,
1117+
end: str = '\n',
1118+
apply_style: bool = True,
1119+
paged: bool = False,
1120+
chop: bool = False,
1121+
) -> None:
1122+
"""Print message to sys.stderr
10881123
10891124
:param msg: object to print
10901125
:param end: string appended after the end of the message, default a newline
1091-
:param apply_style: If True, then ansi.style_success will be applied to the message text. Set to False in cases
1126+
:param apply_style: If True, then ansi.style_error will be applied to the message text. Set to False in cases
10921127
where the message text already has the desired style. Defaults to True.
1128+
:param paged: If True, pass the output through the configured pager.
1129+
:param chop: If paged is True, True to truncate long lines or False to wrap long lines.
10931130
"""
1094-
if apply_style:
1095-
msg = ansi.style_success(msg)
1096-
else:
1097-
msg = str(msg)
1098-
self.poutput(msg, end=end, apply_style=False)
1131+
self.print_to(sys.stderr, msg, end=end, style=ansi.style_error if apply_style else None, paged=paged, chop=chop)
10991132

1100-
# noinspection PyMethodMayBeStatic
1101-
def perror(self, msg: Any = '', *, end: str = '\n', apply_style: bool = True) -> None:
1102-
"""Print message to sys.stderr
1133+
def psuccess(
1134+
self,
1135+
msg: Any = '',
1136+
*,
1137+
end: str = '\n',
1138+
paged: bool = False,
1139+
chop: bool = False,
1140+
) -> None:
1141+
"""Writes to stdout applying ansi.style_success by default
11031142
11041143
:param msg: object to print
11051144
:param end: string appended after the end of the message, default a newline
1106-
:param apply_style: If True, then ansi.style_error will be applied to the message text. Set to False in cases
1107-
where the message text already has the desired style. Defaults to True.
1145+
:param paged: If True, pass the output through the configured pager.
1146+
:param chop: If paged is True, True to truncate long lines or False to wrap long lines.
11081147
"""
1109-
if apply_style:
1110-
final_msg = ansi.style_error(msg)
1111-
else:
1112-
final_msg = str(msg)
1113-
ansi.style_aware_write(sys.stderr, final_msg + end)
1148+
self.print_to(self.stdout, msg, end=end, style=ansi.style_success, paged=paged, chop=chop)
11141149

1115-
def pwarning(self, msg: Any = '', *, end: str = '\n', apply_style: bool = True) -> None:
1150+
def pwarning(
1151+
self,
1152+
msg: Any = '',
1153+
*,
1154+
end: str = '\n',
1155+
apply_style: bool = True,
1156+
paged: bool = False,
1157+
chop: bool = False,
1158+
) -> None:
11161159
"""Wraps perror, but applies ansi.style_warning by default
11171160
11181161
:param msg: object to print
11191162
:param end: string appended after the end of the message, default a newline
11201163
:param apply_style: If True, then ansi.style_warning will be applied to the message text. Set to False in cases
11211164
where the message text already has the desired style. Defaults to True.
1165+
:param paged: If True, pass the output through the configured pager.
1166+
:param chop: If paged is True, True to truncate long lines or False to wrap long lines.
11221167
"""
1123-
if apply_style:
1124-
msg = ansi.style_warning(msg)
1125-
self.perror(msg, end=end, apply_style=False)
1168+
self.print_to(sys.stderr, msg, end=end, style=ansi.style_warning if apply_style else None, paged=paged, chop=chop)
1169+
1170+
def pfailure(
1171+
self,
1172+
msg: Any = '',
1173+
*,
1174+
end: str = '\n',
1175+
paged: bool = False,
1176+
chop: bool = False,
1177+
) -> None:
1178+
"""Writes to stderr applying ansi.style_error by default
1179+
1180+
:param msg: object to print
1181+
:param end: string appended after the end of the message, default a newline
1182+
:param paged: If True, pass the output through the configured pager.
1183+
:param chop: If paged is True, True to truncate long lines or False to wrap long lines.
1184+
"""
1185+
self.print_to(sys.stderr, msg, end=end, style=ansi.style_error, paged=paged, chop=chop)
11261186

11271187
def pexcept(self, msg: Any, *, end: str = '\n', apply_style: bool = True) -> None:
11281188
"""Print Exception message to sys.stderr. If debug is true, print exception traceback if one exists.
@@ -1151,25 +1211,39 @@ def pexcept(self, msg: Any, *, end: str = '\n', apply_style: bool = True) -> Non
11511211

11521212
self.perror(final_msg, end=end, apply_style=False)
11531213

1154-
def pfeedback(self, msg: Any, *, end: str = '\n', apply_style: bool = True) -> None:
1214+
def pfeedback(
1215+
self,
1216+
msg: Any,
1217+
*,
1218+
end: str = '\n',
1219+
apply_style: bool = True,
1220+
paged: bool = False,
1221+
chop: bool = False,
1222+
) -> None:
11551223
"""For printing nonessential feedback. Can be silenced with `quiet`.
11561224
Inclusion in redirected output is controlled by `feedback_to_output`.
11571225
11581226
:param msg: object to print
11591227
:param end: string appended after the end of the message, default a newline
11601228
:param apply_style: If True, then ansi.style_output will be applied to the message text. Set to False in cases
11611229
where the message text already has the desired style. Defaults to True.
1230+
:param paged: If True, pass the output through the configured pager.
1231+
:param chop: If paged is True, True to truncate long lines or False to wrap long lines.
11621232
"""
11631233
if not self.quiet:
1164-
if self.feedback_to_output:
1165-
self.poutput(msg, end=end, apply_style=apply_style)
1166-
else:
1167-
self.perror(msg, end=end, apply_style=False)
1234+
self.print_to(
1235+
self.stdout if self.feedback_to_output else sys.stderr,
1236+
msg,
1237+
end=end,
1238+
style=ansi.style_output if apply_style else None,
1239+
paged=paged,
1240+
chop=chop,
1241+
)
11681242

1169-
def ppaged(self, msg: Any, *, end: str = '\n', chop: bool = False, apply_style: bool = True) -> None:
1243+
def ppaged(self, msg: Any, *, end: str = '\n', chop: bool = False, dest: Optional[Union[TextIO, IO[str]]] = None) -> None:
11701244
"""Print output using a pager if it would go off screen and stdout isn't currently being redirected.
11711245
1172-
Never uses a pager inside of a script (Python or text) or when output is being redirected or piped or when
1246+
Never uses a pager inside a script (Python or text) or when output is being redirected or piped or when
11731247
stdout or stdin are not a fully functional terminal.
11741248
11751249
:param msg: object to print
@@ -1179,13 +1253,13 @@ def ppaged(self, msg: Any, *, end: str = '\n', chop: bool = False, apply_style:
11791253
- chopping is ideal for displaying wide tabular data as is done in utilities like pgcli
11801254
False -> causes lines longer than the screen width to wrap to the next line
11811255
- wrapping is ideal when you want to keep users from having to use horizontal scrolling
1182-
:param apply_style: If True, then ansi.style_output will be applied to the message text. Set to False in cases
1183-
where the message text already has the desired style. Defaults to True.
1256+
:param dest: Optionally specify the destination stream to write to. If unspecified, defaults to self.stdout
11841257
11851258
WARNING: On Windows, the text always wraps regardless of what the chop argument is set to
11861259
"""
11871260
# msg can be any type, so convert to string before checking if it's blank
11881261
msg_str = str(msg)
1262+
dest = self.stdout if dest is None else dest
11891263

11901264
# Consider None to be no data to print
11911265
if msg is None or msg_str == '':
@@ -1219,7 +1293,7 @@ def ppaged(self, msg: Any, *, end: str = '\n', chop: bool = False, apply_style:
12191293
pipe_proc = subprocess.Popen(pager, shell=True, stdin=subprocess.PIPE)
12201294
pipe_proc.communicate(msg_str.encode('utf-8', 'replace'))
12211295
else:
1222-
self.poutput(msg_str, end=end, apply_style=apply_style)
1296+
ansi.style_aware_write(dest, f'{msg_str}{end}')
12231297
except BrokenPipeError:
12241298
# This occurs if a command's output is being piped to another process and that process closes before the
12251299
# command is finished. If you would like your application to print a warning message, then set the

0 commit comments

Comments
 (0)