diff options
-rw-r--r-- | CHANGELOG.md | 6 | ||||
-rw-r--r-- | cmd2/cmd2.py | 39 | ||||
-rwxr-xr-x | examples/paged_output.py | 43 |
3 files changed, 66 insertions, 22 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index 973ed6c6..078af8d8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,12 @@ * Fixed issue where piping and redirecting did not work correctly with paths that had spaces * Enhancements * Added ability to print a header above tab-completion suggestions using `completion_header` member + * Added ``pager`` and ``pager_chop`` attributes to the ``cmd2.Cmd`` class + * ``pager`` defaults to **less -RXF** on POSIX and **more** on Windows + * ``pager_chop`` defaults to **less -SRXF** on POSIX and **more** on Windows + * Added ``chop`` argument to ``cmd2.Cmd.ppaged()`` method for displaying output using a pager + * If ``chop`` is ``False``, then ``self.pager`` is used as the pager + * Otherwise ``self.pager_chop`` is used as the pager ## 0.8.8 (TBD, 2018) * Bug Fixes diff --git a/cmd2/cmd2.py b/cmd2/cmd2.py index 75946764..f143d7b3 100644 --- a/cmd2/cmd2.py +++ b/cmd2/cmd2.py @@ -534,6 +534,18 @@ class Cmd(cmd.Cmd): # quote matches that are completed in a delimited fashion self.matches_delimited = False + # Set the pager(s) for use with the ppaged() method for displaying output using a pager + if sys.platform.startswith('win'): + self.pager = self.pager_chop = 'more' + else: + # Here is the meaning of the various flags we are using with the less command: + # -S causes lines longer than the screen width to be chopped (truncated) rather than wrapped + # -R causes ANSI "color" escape sequences to be output in raw form (i.e. colors are displayed) + # -X disables sending the termcap initialization and deinitialization strings to the terminal + # -F causes less to automatically exit if the entire file can be displayed on the first screen + self.pager = 'less -RXF' + self.pager_chop = 'less -SRXF' + # ----- Methods related to presenting output to the user ----- @property @@ -608,14 +620,20 @@ class Cmd(cmd.Cmd): else: sys.stderr.write("{}\n".format(msg)) - def ppaged(self, msg: str, end: str='\n') -> None: + def ppaged(self, msg: str, 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 of 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: str - message to print to current stdout - anything convertible to a str with '{}'.format() is OK - :param end: str - string appended after the end of the message if not already present, default a newline + :param msg: message to print to current stdout - anything convertible to a str with '{}'.format() is OK + :param end: string appended after the end of the message if not already present, default a newline + :param chop: True -> causes lines longer than the screen width to be chopped (truncated) rather than wrapped + - truncated text is still accessible by scrolling with the right & left arrow keys + - 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 avoid users having to use horizontal scrolling + WARNING: On Windows, the text always wraps regardless of what the chop argument is set to """ import subprocess if msg is not None and msg != '': @@ -635,17 +653,10 @@ class Cmd(cmd.Cmd): # Don't attempt to use a pager that can block if redirecting or running a script (either text or Python) # Also only attempt to use a pager if actually running in a real fully functional terminal if functional_terminal and not self.redirecting and not self._in_py and not self._script_dir: - - if sys.platform.startswith('win'): - pager_cmd = 'more' - else: - # Here is the meaning of the various flags we are using with the less command: - # -S causes lines longer than the screen width to be chopped (truncated) rather than wrapped - # -R causes ANSI "color" escape sequences to be output in raw form (i.e. colors are displayed) - # -X disables sending the termcap initialization and deinitialization strings to the terminal - # -F causes less to automatically exit if the entire file can be displayed on the first screen - pager_cmd = 'less -SRXF' - self.pipe_proc = subprocess.Popen(pager_cmd, shell=True, stdin=subprocess.PIPE) + pager = self.pager + if chop: + pager = self.pager_chop + self.pipe_proc = subprocess.Popen(pager, shell=True, stdin=subprocess.PIPE) try: self.pipe_proc.stdin.write(msg_str.encode('utf-8', 'replace')) self.pipe_proc.stdin.close() diff --git a/examples/paged_output.py b/examples/paged_output.py index c56dcb89..d1b1b2c2 100755 --- a/examples/paged_output.py +++ b/examples/paged_output.py @@ -2,28 +2,55 @@ # coding=utf-8 """A simple example demonstrating the using paged output via the ppaged() method. """ +import os +from typing import List import cmd2 class PagedOutput(cmd2.Cmd): - """ Example cmd2 application where we create commands that just print the arguments they are called with.""" + """ Example cmd2 application which shows how to display output using a pager.""" def __init__(self): super().__init__() + def page_file(self, file_path: str, chop: bool=False): + """Helper method to prevent having too much duplicated code.""" + filename = os.path.expanduser(file_path) + try: + with open(filename, 'r') as f: + text = f.read() + self.ppaged(text, chop=chop) + except FileNotFoundError as ex: + self.perror('ERROR: file {!r} not found'.format(filename), traceback_war=False) + @cmd2.with_argument_list - def do_page_file(self, args): - """Read in a text file and display its output in a pager.""" + def do_page_wrap(self, args: List[str]): + """Read in a text file and display its output in a pager, wrapping long lines if they don't fit. + + Usage: page_wrap <file_path> + """ if not args: - self.perror('page_file requires a path to a file as an argument', traceback_war=False) + self.perror('page_wrap requires a path to a file as an argument', traceback_war=False) return + self.page_file(args[0], chop=False) + + complete_page_wrap = cmd2.Cmd.path_complete + + @cmd2.with_argument_list + def do_page_truncate(self, args: List[str]): + """Read in a text file and display its output in a pager, truncating long lines if they don't fit. + + Truncated lines can still be accessed by scrolling to the right using the arrow keys. - with open(args[0], 'r') as f: - text = f.read() - self.ppaged(text) + Usage: page_chop <file_path> + """ + if not args: + self.perror('page_truncate requires a path to a file as an argument', traceback_war=False) + return + self.page_file(args[0], chop=True) - complete_page_file = cmd2.Cmd.path_complete + complete_page_truncate = cmd2.Cmd.path_complete if __name__ == '__main__': |