Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rolled back undocumented changes to printing functions introduced in 2.5.0. #1395

Merged
merged 2 commits into from
Dec 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 2.5.8 (TBD)
* Bug Fixes
* Rolled back undocumented changes to printing functions introduced in 2.5.0.

## 2.5.7 (November 22, 2024)
* Bug Fixes
* Fixed issue where argument parsers for overridden commands were not being created.
Expand Down
3 changes: 0 additions & 3 deletions cmd2/ansi.py
Original file line number Diff line number Diff line change
Expand Up @@ -1042,9 +1042,6 @@ def style(
# Default styles for printing strings of various types.
# These can be altered to suit an application's needs and only need to be a
# function with the following structure: func(str) -> str
style_output = functools.partial(style)
"""Partial function supplying arguments to :meth:`cmd2.ansi.style()` which colors text for normal output"""

style_success = functools.partial(style, fg=Fg.GREEN)
"""Partial function supplying arguments to :meth:`cmd2.ansi.style()` which colors text to signify success"""

Expand Down
155 changes: 40 additions & 115 deletions cmd2/cmd2.py
Original file line number Diff line number Diff line change
Expand Up @@ -1212,123 +1212,67 @@ def visible_prompt(self) -> str:

def print_to(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a lot cleaner here. Now print_to is doing one thing and doing it well. I'd recommend adding a brief docstring to explain the purpose.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added docstring.

self,
dest: Union[TextIO, IO[str]],
dest: 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)
"""
Print message to a given file object.

def poutput(
self,
msg: Any = '',
*,
end: str = '\n',
apply_style: bool = True,
paged: bool = False,
chop: bool = False,
) -> None:
:param dest: the file object being written to
:param msg: object to print
:param end: string appended after the end of the message, default a newline
:param style: optional style function to format msg with (e.g. ansi.style_success)
"""
final_msg = style(msg) if style is not None else msg
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') -> None:
"""Print message to self.stdout and appends a newline by default

Also handles BrokenPipeError exceptions for when a command's output has
been piped to another process and that process terminates before the
cmd2 command is finished executing.

: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.
"""
self.print_to(self.stdout, msg, end=end, style=ansi.style_output if apply_style else None, paged=paged, chop=chop)
self.print_to(self.stdout, msg, end=end)

def perror(
self,
msg: Any = '',
*,
end: str = '\n',
apply_style: bool = True,
paged: bool = False,
chop: bool = False,
) -> None:
def perror(self, msg: Any = '', *, end: str = '\n', apply_style: bool = True) -> 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_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.
"""
self.print_to(sys.stderr, msg, end=end, style=ansi.style_error if apply_style else None, paged=paged, chop=chop)
self.print_to(sys.stderr, msg, end=end, style=ansi.style_error if apply_style else None)

def psuccess(
self,
msg: Any = '',
*,
end: str = '\n',
paged: bool = False,
chop: bool = False,
) -> None:
"""Writes to stdout applying ansi.style_success by default
def psuccess(self, msg: Any = '', *, end: str = '\n') -> None:
"""Wraps poutput, but applies ansi.style_success 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(self.stdout, msg, end=end, style=ansi.style_success, paged=paged, chop=chop)
msg = ansi.style_success(msg)
self.poutput(msg, end=end)

def pwarning(
self,
msg: Any = '',
*,
end: str = '\n',
paged: bool = False,
chop: bool = False,
) -> None:
def pwarning(self, msg: Any = '', *, end: str = '\n') -> 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 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_warning, 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)
msg = ansi.style_warning(msg)
self.perror(msg, end=end, apply_style=False)

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 @@ -1357,36 +1301,20 @@ 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,
paged: bool = False,
chop: bool = False,
) -> None:
def pfeedback(self, msg: Any, *, end: str = '\n') -> 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:
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,
)
if self.feedback_to_output:
self.poutput(msg, end=end)
else:
self.perror(msg, end=end, apply_style=False)

def ppaged(self, msg: Any, *, end: str = '\n', chop: bool = False, dest: Optional[Union[TextIO, IO[str]]] = None) -> None:
def ppaged(self, msg: Any, *, end: str = '\n', chop: bool = False) -> None:
"""Print output using a pager if it would go off screen and stdout isn't currently being redirected.

Never uses a pager inside a script (Python or text) or when output is being redirected or piped or when
Expand All @@ -1399,17 +1327,14 @@ def ppaged(self, msg: Any, *, end: str = '\n', chop: bool = False, dest: Optiona
- 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 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
"""
dest = self.stdout if dest is None else dest

# Attempt to detect if we are not running within a fully functional terminal.
# Don't try to use the pager when being run by a continuous integration system like Jenkins + pexpect.
functional_terminal = False

if self.stdin.isatty() and dest.isatty():
if self.stdin.isatty() and self.stdout.isatty():
if sys.platform.startswith('win') or os.environ.get('TERM') is not None:
functional_terminal = True

Expand All @@ -1430,7 +1355,7 @@ def ppaged(self, msg: Any, *, end: str = '\n', chop: bool = False, dest: Optiona
with self.sigint_protection:
import subprocess

pipe_proc = subprocess.Popen(pager, shell=True, stdin=subprocess.PIPE, stdout=dest)
pipe_proc = subprocess.Popen(pager, shell=True, stdin=subprocess.PIPE, stdout=self.stdout)
pipe_proc.communicate(final_msg.encode('utf-8', 'replace'))
except BrokenPipeError:
# This occurs if a command's output is being piped to another process and that process closes before the
Expand All @@ -1439,7 +1364,7 @@ def ppaged(self, msg: Any, *, end: str = '\n', chop: bool = False, dest: Optiona
if self.broken_pipe_warning:
sys.stderr.write(self.broken_pipe_warning)
else:
self.print_to(dest, msg, end=end, paged=False)
self.poutput(msg, end=end)

# ----- Methods related to tab completion -----

Expand Down
2 changes: 1 addition & 1 deletion examples/colors.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ def do_speak(self, args):

for _ in range(min(repetitions, self.maxrepeats)):
# .poutput handles newlines, and accommodates output redirection too
self.poutput(output_str, apply_style=False)
self.poutput(output_str)

def do_timetravel(self, _):
"""A command which always generates an error message, to demonstrate custom error colors"""
Expand Down
2 changes: 1 addition & 1 deletion examples/initialization.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ def do_intro(self, _):
def do_echo(self, arg):
"""Example of a multiline command"""
fg_color = Fg[self.foreground_color.upper()]
self.poutput(style(arg, fg=fg_color), apply_style=False)
self.poutput(style(arg, fg=fg_color))


if __name__ == '__main__':
Expand Down
2 changes: 1 addition & 1 deletion examples/pirate.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ def do_quit(self, arg):

def do_sing(self, arg):
"""Sing a colorful song."""
self.poutput(cmd2.ansi.style(arg, fg=Fg[self.songcolor.upper()]), apply_style=False)
self.poutput(cmd2.ansi.style(arg, fg=Fg[self.songcolor.upper()]))

yo_parser = cmd2.Cmd2ArgumentParser()
yo_parser.add_argument('--ho', type=int, default=2, help="How often to chant 'ho'")
Expand Down
4 changes: 2 additions & 2 deletions tests/test_cmd2.py
Original file line number Diff line number Diff line change
Expand Up @@ -1992,7 +1992,7 @@ def test_poutput_none(outsim_app):
def test_poutput_ansi_always(outsim_app):
msg = 'Hello World'
colored_msg = ansi.style(msg, fg=ansi.Fg.CYAN)
outsim_app.poutput(colored_msg, apply_style=False)
outsim_app.poutput(colored_msg)
out = outsim_app.stdout.getvalue()
expected = colored_msg + '\n'
assert colored_msg != msg
Expand All @@ -2003,7 +2003,7 @@ def test_poutput_ansi_always(outsim_app):
def test_poutput_ansi_never(outsim_app):
msg = 'Hello World'
colored_msg = ansi.style(msg, fg=ansi.Fg.CYAN)
outsim_app.poutput(colored_msg, apply_style=False)
outsim_app.poutput(colored_msg)
out = outsim_app.stdout.getvalue()
expected = msg + '\n'
assert colored_msg != msg
Expand Down
Loading