diff --git a/CHANGELOG.md b/CHANGELOG.md index ec86a319..d4c83c83 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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. diff --git a/cmd2/ansi.py b/cmd2/ansi.py index 52bf382a..13d7bb67 100644 --- a/cmd2/ansi.py +++ b/cmd2/ansi.py @@ -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""" diff --git a/cmd2/cmd2.py b/cmd2/cmd2.py index ea53d345..70c915f5 100644 --- a/cmd2/cmd2.py +++ b/cmd2/cmd2.py @@ -1212,123 +1212,67 @@ def visible_prompt(self) -> str: def print_to( 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. @@ -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 @@ -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 @@ -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 @@ -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 ----- diff --git a/examples/colors.py b/examples/colors.py index 3f81d18c..34f16da2 100755 --- a/examples/colors.py +++ b/examples/colors.py @@ -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""" diff --git a/examples/initialization.py b/examples/initialization.py index 426a5a4a..8cdf0734 100755 --- a/examples/initialization.py +++ b/examples/initialization.py @@ -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__': diff --git a/examples/pirate.py b/examples/pirate.py index db5d14fc..8c443d36 100755 --- a/examples/pirate.py +++ b/examples/pirate.py @@ -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'") diff --git a/tests/test_cmd2.py b/tests/test_cmd2.py index 1df1ea2c..721be0a2 100755 --- a/tests/test_cmd2.py +++ b/tests/test_cmd2.py @@ -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 @@ -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