Skip to content

Commit

Permalink
Reorganized a bit to move towards separation between where to print f…
Browse files Browse the repository at this point in the history
…rom what and how to print
  • Loading branch information
anselor committed Jun 28, 2023
1 parent 7a2c384 commit d5508e9
Show file tree
Hide file tree
Showing 2 changed files with 130 additions and 56 deletions.
8 changes: 4 additions & 4 deletions cmd2/argparse_custom.py
Original file line number Diff line number Diff line change
Expand Up @@ -1118,7 +1118,7 @@ def _format_usage(

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

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

# break usage into wrappable parts
part_regexp = r'\(.*?\)+|\[.*?\]+|\S+'
req_usage = format(required_options, groups)
opt_usage = format(optionals, groups)
pos_usage = format(positionals, groups)
req_usage = format(required_options, groups) # type: ignore[arg-type]
opt_usage = format(optionals, groups) # type: ignore[arg-type]
pos_usage = format(positionals, groups) # type: ignore[arg-type]
req_parts = re.findall(part_regexp, req_usage)
opt_parts = re.findall(part_regexp, opt_usage)
pos_parts = re.findall(part_regexp, pos_usage)
Expand Down
178 changes: 126 additions & 52 deletions cmd2/cmd2.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
ModuleType,
)
from typing import (
IO,
Any,
Callable,
Dict,
Expand Down Expand Up @@ -601,11 +602,14 @@ def register_command_set(self, cmdset: CommandSet) -> None:
raise CommandSetRegistrationError(f'Duplicate settable {key} is already registered')

cmdset.on_register(self)
methods = inspect.getmembers(
cmdset,
predicate=lambda meth: isinstance(meth, Callable) # type: ignore[arg-type]
and hasattr(meth, '__name__')
and meth.__name__.startswith(COMMAND_FUNC_PREFIX),
methods = cast(
List[Tuple[str, Callable[..., Any]]],
inspect.getmembers(
cmdset,
predicate=lambda meth: isinstance(meth, Callable) # type: ignore[arg-type]
and hasattr(meth, '__name__')
and meth.__name__.startswith(COMMAND_FUNC_PREFIX),
),
)

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

def poutput(self, msg: Any = '', *, end: str = '\n', apply_style: bool = True) -> None:
def print_to(
self,
dest: Union[TextIO, IO[str]],
msg: Any,
*,
end: str = '\n',
style: Optional[Callable[[str], str]] = None,
paged: bool = False,
chop: bool = False,
) -> None:
final_msg = style(msg) if style is not None else msg
if paged:
self.ppaged(final_msg, end=end, chop=chop, dest=dest)
else:
try:
ansi.style_aware_write(dest, f'{final_msg}{end}')
except BrokenPipeError:
# This occurs if a command's output is being piped to another
# process and that process closes before the command is
# finished. If you would like your application to print a
# warning message, then set the broken_pipe_warning attribute
# to the message you want printed.
if self.broken_pipe_warning:
sys.stderr.write(self.broken_pipe_warning)

def poutput(
self,
msg: Any = '',
*,
end: str = '\n',
apply_style: bool = True,
paged: bool = False,
chop: bool = False,
) -> None:
"""Print message to self.stdout and appends a newline by default
Also handles BrokenPipeError exceptions for when a command's output has
Expand All @@ -1067,62 +1104,85 @@ def poutput(self, msg: Any = '', *, end: str = '\n', apply_style: bool = True) -
:param end: string appended after the end of the message, default a newline
:param apply_style: If True, then ansi.style_output will be applied to the message text. Set to False in cases
where the message text already has the desired style. Defaults to True.
:param paged: If True, pass the output through the configured pager.
:param chop: If paged is True, True to truncate long lines or False to wrap long lines.
"""
if apply_style:
final_msg = ansi.style_output(msg)
else:
final_msg = str(msg)
try:
ansi.style_aware_write(self.stdout, f"{final_msg}{end}")
except BrokenPipeError:
# This occurs if a command's output is being piped to another
# process and that process closes before the command is
# finished. If you would like your application to print a
# warning message, then set the broken_pipe_warning attribute
# to the message you want printed.
if self.broken_pipe_warning:
sys.stderr.write(self.broken_pipe_warning)
self.print_to(self.stdout, msg, end=end, style=ansi.style_output if apply_style else None, paged=paged, chop=chop)

def psuccess(self, msg: Any = '', *, end: str = '\n', apply_style: bool = True) -> None:
"""Wraps poutput but applies ansi.style_success by default
# noinspection PyMethodMayBeStatic
def perror(
self,
msg: Any = '',
*,
end: str = '\n',
apply_style: bool = True,
paged: bool = False,
chop: bool = False,
) -> None:
"""Print message to sys.stderr
:param msg: object to print
:param end: string appended after the end of the message, default a newline
:param apply_style: If True, then ansi.style_success will be applied to the message text. Set to False in cases
:param apply_style: If True, then ansi.style_error will be applied to the message text. Set to False in cases
where the message text already has the desired style. Defaults to True.
:param paged: If True, pass the output through the configured pager.
:param chop: If paged is True, True to truncate long lines or False to wrap long lines.
"""
if apply_style:
msg = ansi.style_success(msg)
else:
msg = str(msg)
self.poutput(msg, end=end, apply_style=False)
self.print_to(sys.stderr, msg, end=end, style=ansi.style_error if apply_style else None, paged=paged, chop=chop)

# noinspection PyMethodMayBeStatic
def perror(self, msg: Any = '', *, end: str = '\n', apply_style: bool = True) -> None:
"""Print message to sys.stderr
def psuccess(
self,
msg: Any = '',
*,
end: str = '\n',
paged: bool = False,
chop: bool = False,
) -> None:
"""Writes to stdout applying ansi.style_success by default
:param msg: object to print
:param end: string appended after the end of the message, default a newline
:param apply_style: If True, then ansi.style_error will be applied to the message text. Set to False in cases
where the message text already has the desired style. Defaults to True.
:param paged: If True, pass the output through the configured pager.
:param chop: If paged is True, True to truncate long lines or False to wrap long lines.
"""
if apply_style:
final_msg = ansi.style_error(msg)
else:
final_msg = str(msg)
ansi.style_aware_write(sys.stderr, final_msg + end)
self.print_to(self.stdout, msg, end=end, style=ansi.style_success, paged=paged, chop=chop)

def pwarning(self, msg: Any = '', *, end: str = '\n', apply_style: bool = True) -> None:
def pwarning(
self,
msg: Any = '',
*,
end: str = '\n',
apply_style: bool = True,
paged: bool = False,
chop: bool = False,
) -> None:
"""Wraps perror, but applies ansi.style_warning by default
:param msg: object to print
:param end: string appended after the end of the message, default a newline
:param apply_style: If True, then ansi.style_warning will be applied to the message text. Set to False in cases
where the message text already has the desired style. Defaults to True.
:param paged: If True, pass the output through the configured pager.
:param chop: If paged is True, True to truncate long lines or False to wrap long lines.
"""
if apply_style:
msg = ansi.style_warning(msg)
self.perror(msg, end=end, apply_style=False)
self.print_to(sys.stderr, msg, end=end, style=ansi.style_warning if apply_style else None, paged=paged, chop=chop)

def pfailure(
self,
msg: Any = '',
*,
end: str = '\n',
paged: bool = False,
chop: bool = False,
) -> None:
"""Writes to stderr applying ansi.style_error by default
:param msg: object to print
:param end: string appended after the end of the message, default a newline
:param paged: If True, pass the output through the configured pager.
:param chop: If paged is True, True to truncate long lines or False to wrap long lines.
"""
self.print_to(sys.stderr, msg, end=end, style=ansi.style_error, paged=paged, chop=chop)

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

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

def pfeedback(self, msg: Any, *, end: str = '\n', apply_style: bool = True) -> None:
def pfeedback(
self,
msg: Any,
*,
end: str = '\n',
apply_style: bool = True,
paged: bool = False,
chop: bool = False,
) -> None:
"""For printing nonessential feedback. Can be silenced with `quiet`.
Inclusion in redirected output is controlled by `feedback_to_output`.
:param msg: object to print
:param end: string appended after the end of the message, default a newline
:param apply_style: If True, then ansi.style_output will be applied to the message text. Set to False in cases
where the message text already has the desired style. Defaults to True.
:param paged: If True, pass the output through the configured pager.
:param chop: If paged is True, True to truncate long lines or False to wrap long lines.
"""
if not self.quiet:
if self.feedback_to_output:
self.poutput(msg, end=end, apply_style=apply_style)
else:
self.perror(msg, end=end, apply_style=False)
self.print_to(
self.stdout if self.feedback_to_output else sys.stderr,
msg,
end=end,
style=ansi.style_output if apply_style else None,
paged=paged,
chop=chop,
)

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

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

0 comments on commit d5508e9

Please sign in to comment.