@@ -1133,6 +1133,9 @@ def __init__(self, completekey='tab', stdin=None, stdout=None, persistent_histor
11331133 # Used by complete() for readline tab completion
11341134 self .completion_matches = []
11351135
1136+ # Used to keep track of whether we are redirecting or piping output
1137+ self .redirecting = False
1138+
11361139 # ----- Methods related to presenting output to the user -----
11371140
11381141 @property
@@ -1154,7 +1157,7 @@ def _finalize_app_parameters(self):
11541157 # Make sure settable parameters are sorted alphabetically by key
11551158 self .settable = collections .OrderedDict (sorted (self .settable .items (), key = lambda t : t [0 ]))
11561159
1157- def poutput (self , msg , end = ' \n ' ):
1160+ def poutput (self , msg , end = os . linesep ):
11581161 """Convenient shortcut for self.stdout.write(); by default adds newline to end if not already present.
11591162
11601163 Also handles BrokenPipeError exceptions for when a commands's output has been piped to another process and
@@ -1207,6 +1210,55 @@ def pfeedback(self, msg):
12071210 else :
12081211 sys .stderr .write ("{}\n " .format (msg ))
12091212
1213+ def ppaged (self , msg , end = os .linesep ):
1214+ """Print output using a pager if it would go off screen and stdout isn't currently being redirected.
1215+
1216+ Never uses a pager inside of a script (Python or text) or when output is being redirected or piped.
1217+
1218+ :param msg: str - message to print to current stdout - anything convertible to a str with '{}'.format() is OK
1219+ :param end: str - string appended after the end of the message if not already present, default a newline
1220+ """
1221+ if msg is not None and msg != '' :
1222+ try :
1223+ msg_str = '{}' .format (msg )
1224+ if not msg_str .endswith (end ):
1225+ msg_str += end
1226+
1227+ # Don't attempt to use a pager that can block if redirecting or running a script (either text or Python)
1228+ if not self .redirecting and not self ._in_py and not self ._script_dir :
1229+ # Here is the meaning of the various flags we are using with the less command:
1230+ # -S causes lines longer than the screen width to be chopped (truncated) rather than wrapped
1231+ # -R causes ANSI "color" escape sequences to be output in raw form (i.e. colors are displayed)
1232+ # -X disables sending the termcap initialization and deinitialization strings to the terminal
1233+ # -F causes less to automatically exit if the entire file can be displayed on the first screen
1234+ pager_cmd = 'less -SRXF'
1235+ if sys .platform .startswith ('win' ):
1236+ pager_cmd = 'more'
1237+ self .pipe_proc = subprocess .Popen (pager_cmd , shell = True , stdin = subprocess .PIPE )
1238+ try :
1239+ self .pipe_proc .stdin .write (msg_str .encode ('utf-8' , 'replace' ))
1240+ self .pipe_proc .stdin .close ()
1241+ except (IOError , KeyboardInterrupt ):
1242+ pass
1243+
1244+ # Less doesn't respect ^C, but catches it for its own UI purposes (aborting search etc. inside less)
1245+ while True :
1246+ try :
1247+ self .pipe_proc .wait ()
1248+ except KeyboardInterrupt :
1249+ pass
1250+ else :
1251+ break
1252+ self .pipe_proc = None
1253+ else :
1254+ self .stdout .write (msg_str )
1255+ except BROKEN_PIPE_ERROR :
1256+ # This occurs if a command's output is being piped to another process and that process closes before the
1257+ # command is finished. We intentionally don't print a warning message here since we know that stdout
1258+ # will be restored by the _restore_output() method. If you would like your application to print a
1259+ # warning message, then override this method.
1260+ pass
1261+
12101262 def colorize (self , val , color ):
12111263 """Given a string (``val``), returns that string wrapped in UNIX-style
12121264 special characters that turn on (and then off) text color and style.
@@ -1599,6 +1651,7 @@ def _redirect_output(self, statement):
15991651 # Open each side of the pipe and set stdout accordingly
16001652 # noinspection PyTypeChecker
16011653 self .stdout = io .open (write_fd , write_mode )
1654+ self .redirecting = True
16021655 # noinspection PyTypeChecker
16031656 subproc_stdin = io .open (read_fd , read_mode )
16041657
@@ -1612,6 +1665,7 @@ def _redirect_output(self, statement):
16121665 self .pipe_proc = None
16131666 self .kept_state .restore ()
16141667 self .kept_state = None
1668+ self .redirecting = False
16151669
16161670 # Re-raise the exception
16171671 raise ex
@@ -1620,6 +1674,7 @@ def _redirect_output(self, statement):
16201674 raise EnvironmentError ('Cannot redirect to paste buffer; install ``xclip`` and re-run to enable' )
16211675 self .kept_state = Statekeeper (self , ('stdout' ,))
16221676 self .kept_sys = Statekeeper (sys , ('stdout' ,))
1677+ self .redirecting = True
16231678 if statement .parsed .outputTo :
16241679 mode = 'w'
16251680 if statement .parsed .output == 2 * self .redirector :
@@ -1662,6 +1717,8 @@ def _restore_output(self, statement):
16621717 self .kept_sys .restore ()
16631718 self .kept_sys = None
16641719
1720+ self .redirecting = False
1721+
16651722 def _func_named (self , arg ):
16661723 """Gets the method name associated with a given command.
16671724
0 commit comments