Skip to content

Commit c8b8c49

Browse files
authored
Merge pull request #441 from python-cmd2/ppaged_pager_env
Added pager and pager_chop attributes to Cmd class
2 parents 878a662 + 4869082 commit c8b8c49

File tree

3 files changed

+66
-22
lines changed

3 files changed

+66
-22
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,12 @@
33
* Fixed issue where piping and redirecting did not work correctly with paths that had spaces
44
* Enhancements
55
* Added ability to print a header above tab-completion suggestions using `completion_header` member
6+
* Added ``pager`` and ``pager_chop`` attributes to the ``cmd2.Cmd`` class
7+
* ``pager`` defaults to **less -RXF** on POSIX and **more** on Windows
8+
* ``pager_chop`` defaults to **less -SRXF** on POSIX and **more** on Windows
9+
* Added ``chop`` argument to ``cmd2.Cmd.ppaged()`` method for displaying output using a pager
10+
* If ``chop`` is ``False``, then ``self.pager`` is used as the pager
11+
* Otherwise ``self.pager_chop`` is used as the pager
612

713
## 0.8.8 (TBD, 2018)
814
* Bug Fixes

cmd2/cmd2.py

Lines changed: 25 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -534,6 +534,18 @@ def __init__(self, completekey: str='tab', stdin=None, stdout=None, persistent_h
534534
# quote matches that are completed in a delimited fashion
535535
self.matches_delimited = False
536536

537+
# Set the pager(s) for use with the ppaged() method for displaying output using a pager
538+
if sys.platform.startswith('win'):
539+
self.pager = self.pager_chop = 'more'
540+
else:
541+
# Here is the meaning of the various flags we are using with the less command:
542+
# -S causes lines longer than the screen width to be chopped (truncated) rather than wrapped
543+
# -R causes ANSI "color" escape sequences to be output in raw form (i.e. colors are displayed)
544+
# -X disables sending the termcap initialization and deinitialization strings to the terminal
545+
# -F causes less to automatically exit if the entire file can be displayed on the first screen
546+
self.pager = 'less -RXF'
547+
self.pager_chop = 'less -SRXF'
548+
537549
# ----- Methods related to presenting output to the user -----
538550

539551
@property
@@ -608,14 +620,20 @@ def pfeedback(self, msg: str) -> None:
608620
else:
609621
sys.stderr.write("{}\n".format(msg))
610622

611-
def ppaged(self, msg: str, end: str='\n') -> None:
623+
def ppaged(self, msg: str, end: str='\n', chop: bool=False) -> None:
612624
"""Print output using a pager if it would go off screen and stdout isn't currently being redirected.
613625
614626
Never uses a pager inside of a script (Python or text) or when output is being redirected or piped or when
615627
stdout or stdin are not a fully functional terminal.
616628
617-
:param msg: str - message to print to current stdout - anything convertible to a str with '{}'.format() is OK
618-
:param end: str - string appended after the end of the message if not already present, default a newline
629+
:param msg: message to print to current stdout - anything convertible to a str with '{}'.format() is OK
630+
:param end: string appended after the end of the message if not already present, default a newline
631+
:param chop: True -> causes lines longer than the screen width to be chopped (truncated) rather than wrapped
632+
- truncated text is still accessible by scrolling with the right & left arrow keys
633+
- chopping is ideal for displaying wide tabular data as is done in utilities like pgcli
634+
False -> causes lines longer than the screen width to wrap to the next line
635+
- wrapping is ideal when you want to avoid users having to use horizontal scrolling
636+
WARNING: On Windows, the text always wraps regardless of what the chop argument is set to
619637
"""
620638
import subprocess
621639
if msg is not None and msg != '':
@@ -635,17 +653,10 @@ def ppaged(self, msg: str, end: str='\n') -> None:
635653
# Don't attempt to use a pager that can block if redirecting or running a script (either text or Python)
636654
# Also only attempt to use a pager if actually running in a real fully functional terminal
637655
if functional_terminal and not self.redirecting and not self._in_py and not self._script_dir:
638-
639-
if sys.platform.startswith('win'):
640-
pager_cmd = 'more'
641-
else:
642-
# Here is the meaning of the various flags we are using with the less command:
643-
# -S causes lines longer than the screen width to be chopped (truncated) rather than wrapped
644-
# -R causes ANSI "color" escape sequences to be output in raw form (i.e. colors are displayed)
645-
# -X disables sending the termcap initialization and deinitialization strings to the terminal
646-
# -F causes less to automatically exit if the entire file can be displayed on the first screen
647-
pager_cmd = 'less -SRXF'
648-
self.pipe_proc = subprocess.Popen(pager_cmd, shell=True, stdin=subprocess.PIPE)
656+
pager = self.pager
657+
if chop:
658+
pager = self.pager_chop
659+
self.pipe_proc = subprocess.Popen(pager, shell=True, stdin=subprocess.PIPE)
649660
try:
650661
self.pipe_proc.stdin.write(msg_str.encode('utf-8', 'replace'))
651662
self.pipe_proc.stdin.close()

examples/paged_output.py

Lines changed: 35 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,28 +2,55 @@
22
# coding=utf-8
33
"""A simple example demonstrating the using paged output via the ppaged() method.
44
"""
5+
import os
6+
from typing import List
57

68
import cmd2
79

810

911
class PagedOutput(cmd2.Cmd):
10-
""" Example cmd2 application where we create commands that just print the arguments they are called with."""
12+
""" Example cmd2 application which shows how to display output using a pager."""
1113

1214
def __init__(self):
1315
super().__init__()
1416

17+
def page_file(self, file_path: str, chop: bool=False):
18+
"""Helper method to prevent having too much duplicated code."""
19+
filename = os.path.expanduser(file_path)
20+
try:
21+
with open(filename, 'r') as f:
22+
text = f.read()
23+
self.ppaged(text, chop=chop)
24+
except FileNotFoundError as ex:
25+
self.perror('ERROR: file {!r} not found'.format(filename), traceback_war=False)
26+
1527
@cmd2.with_argument_list
16-
def do_page_file(self, args):
17-
"""Read in a text file and display its output in a pager."""
28+
def do_page_wrap(self, args: List[str]):
29+
"""Read in a text file and display its output in a pager, wrapping long lines if they don't fit.
30+
31+
Usage: page_wrap <file_path>
32+
"""
1833
if not args:
19-
self.perror('page_file requires a path to a file as an argument', traceback_war=False)
34+
self.perror('page_wrap requires a path to a file as an argument', traceback_war=False)
2035
return
36+
self.page_file(args[0], chop=False)
37+
38+
complete_page_wrap = cmd2.Cmd.path_complete
39+
40+
@cmd2.with_argument_list
41+
def do_page_truncate(self, args: List[str]):
42+
"""Read in a text file and display its output in a pager, truncating long lines if they don't fit.
43+
44+
Truncated lines can still be accessed by scrolling to the right using the arrow keys.
2145
22-
with open(args[0], 'r') as f:
23-
text = f.read()
24-
self.ppaged(text)
46+
Usage: page_chop <file_path>
47+
"""
48+
if not args:
49+
self.perror('page_truncate requires a path to a file as an argument', traceback_war=False)
50+
return
51+
self.page_file(args[0], chop=True)
2552

26-
complete_page_file = cmd2.Cmd.path_complete
53+
complete_page_truncate = cmd2.Cmd.path_complete
2754

2855

2956
if __name__ == '__main__':

0 commit comments

Comments
 (0)