Belle II Software development
Pager Class Reference

Public Member Functions

 __init__ (self, prompt=None, quit_if_one_screen=False)
 
 __enter__ (self)
 
 __exit__ (self, exc_type, exc_val, exc_tb)
 

Protected Attributes

str _pager = os.environ.get("PAGER", "less")
 pager program to use
 
str _prompt = prompt
 prompt string
 
 _quit_if_one_screen = quit_if_one_screen
 flag indicating whether the pager should automatically exit if the content fits on one screen
 
 _pager_process = None
 Pager subprocess.
 
 _original_stdout_fd = None
 Original file descriptor for stdout before entering the context.
 
 _original_stderr_fd = None
 Original file descriptor for stderr before entering the context.
 
 _original_stdout = None
 Original sys.__stdout__ before entering the context.
 
 _original_stderr = None
 Original sys.__stderr__ before entering the context.
 
 _original_stdout_isatty = None
 Original sys.stdout.isatty.
 
 _original_stderr_isatty = None
 Original sys.stderr.isatty.
 

Detailed Description

Context manager providing page-wise output using ``less``, similar to how
git handles long output of for example ``git diff``.  Paging will only be
active if the output is to a terminal and not piped into a file or to a
different program.

Warning:
    To be able to see `basf2` log messages like `B2INFO() <basf2.B2INFO>`
    on the paged output you have to set
    `basf2.logging.enable_python_logging = True
    <basf2.LogPythonInterface.enable_python_logging>`

.. versionchanged:: release-03-00-00
   the pager no longer waits until all output is complete but can
   incrementally show output. It can also show output generated in C++

You can set the environment variable ``$PAGER`` to an empty string or to
``cat`` to disable paging or to a different program (for example ``more``)
which should retrieve the output and display it.

>>> with Pager():
>>>     for i in range(30):
>>>         print("This is an example on how to use the pager.")

Parameters:
    prompt (str): a string argument allows overriding the description
        provided by ``less``. Special characters may need escaping.
        Will only be shown if paging is used and the pager is actually ``less``.
    quit_if_one_screen (bool): indicating whether the Pager should quit
        automatically if the content fits on one screen. This implies that
        the content stays visible on pager exit. True is similar to the
        behavior of :program:`git diff`, False is similar to :program:`git
        --help`

Definition at line 159 of file terminal_utils.py.

Constructor & Destructor Documentation

◆ __init__()

__init__ ( self,
prompt = None,
quit_if_one_screen = False )
 constructor just remembering the arguments 

Definition at line 195 of file terminal_utils.py.

195 def __init__(self, prompt=None, quit_if_one_screen=False):
196 """ constructor just remembering the arguments """
197
198 self._pager = os.environ.get("PAGER", "less")
199 # treat "cat" as no pager at all
200 if self._pager == "cat":
201 self._pager = ""
202
203 self._prompt = prompt
204
206 self._quit_if_one_screen = quit_if_one_screen
207
208 self._pager_process = None
209
210 self._original_stdout_fd = None
211
212 self._original_stderr_fd = None
213
214 self._original_stdout = None
215
216 self._original_stderr = None
217
218 self._original_stdout_isatty = None
219
220 self._original_stderr_isatty = None
221

Member Function Documentation

◆ __enter__()

__enter__ ( self)
 entering context 

Definition at line 222 of file terminal_utils.py.

222 def __enter__(self):
223 """ entering context """
224 if not sys.stdout.isatty() or self._pager == "":
225 return
226
227 # save old sys.__stderr__ and sys.__stdout__ objects
228 self._original_stderr = sys.__stderr__
229 self._original_stderr = sys.__stdout__
230 try:
231 # and duplicate the current output file descriptors
232 self._original_stdout_fd = os.dup(sys.stdout.fileno())
233 self._original_stderr_fd = os.dup(sys.stderr.fileno())
234 except AttributeError:
235 # jupyter notebook stdout/stderr objects don't have a fileno so
236 # don't support paging
237 return
238
239 # This is a bit annoying: Usually in python the sys.__stdout__ and
240 # sys.__stderr__ objects point to the original stdout/stderr on program start.
241 #
242 # However we modify the file descriptors directly so these objects will
243 # also be redirected automatically. The documentation for
244 # sys.__stdout__ says it "could be useful to print to the actual
245 # standard stream no matter if the sys.std* object has been
246 # redirected". Also, querying the terminal size looks at
247 # sys.__stdout__ which would no longer be pointing to a tty.
248 #
249 # So lets provide objects pointing to the original file descriptors so
250 # that they behave as expected, i.e. as if we would only have
251 # redirected sys.stdout and sys.stderr ...
252 sys.__stdout__ = io.TextIOWrapper(os.fdopen(self._original_stdout_fd, "wb"))
253 sys.__stderr__ = io.TextIOWrapper(os.fdopen(self._original_stderr_fd, "wb"))
254
255 # also monkey patch the isatty() function of stdout to actually keep
256 # returning True even if we moved the file descriptor
257 self._original_stdout_isatty = sys.stdout.isatty
258 sys.stdout.isatty = lambda: True
259 self._original_stderr_isatty = sys.stderr.isatty
260 sys.stderr.isatty = lambda: True
261
262 # fine, everything is saved, start the pager
263 pager_cmd = [self._pager]
264 if self._pager == "less":
265 if self._prompt is None:
266 self._prompt = '' # same as default prompt
267 self._prompt += ' (press h for help or q to quit)'
268 pager_cmd += ['-R', '-Ps' + self._prompt.strip()]
269 if self._quit_if_one_screen:
270 pager_cmd += ['-F', '-X']
271 self._pager_process = subprocess.Popen(pager_cmd + ["-"], restore_signals=True,
272 stdin=subprocess.PIPE)
273 # and attach stdout to the pager stdin
274 pipe_fd = self._pager_process.stdin.fileno()
275 # and if stderr was a tty do the same for stderr
276 os.dup2(pipe_fd, sys.stdout.fileno())
277 if sys.stderr.isatty():
278 os.dup2(pipe_fd, sys.stderr.fileno())
279

◆ __exit__()

__exit__ ( self,
exc_type,
exc_val,
exc_tb )
 exiting context 

Definition at line 280 of file terminal_utils.py.

280 def __exit__(self, exc_type, exc_val, exc_tb):
281 """ exiting context """
282 # no pager, nothing to do
283 if self._pager_process is None:
284 return
285
286 # otherwise let's try to flush whatever is left
287 try:
288 sys.stdout.flush()
289 except BrokenPipeError:
290 # apparently pager died before we could flush ... so let's move the
291 # remaining output to /dev/null and flush whatever is left
292 devnull = os.open(os.devnull, os.O_WRONLY)
293 os.dup2(devnull, sys.stdout.fileno())
294 sys.stdout.flush()
295
296 # restore output
297 os.dup2(self._original_stdout_fd, sys.stdout.fileno())
298 os.dup2(self._original_stderr_fd, sys.stderr.fileno())
299 # and the original __stdout__/__stderr__ object just in case. Will also
300 # close the copied file descriptors
301 sys.__stdout__.close()
302 sys.__stderr__.close()
303 sys.__stderr__ = self._original_stderr
304 sys.__stdout__ = self._original_stdout
305 # and clean up our monkey patch of isatty
306 sys.stdout.isatty = self._original_stdout_isatty
307 sys.stderr.isatty = self._original_stderr_isatty
308
309 # wait for pager
310 self._pager_process.communicate()
311
312 # and if we exited due to broken pipe ... then ignore it
313 return exc_type == BrokenPipeError
314
315

Member Data Documentation

◆ _original_stderr

_original_stderr = None
protected

Original sys.__stderr__ before entering the context.

Definition at line 216 of file terminal_utils.py.

◆ _original_stderr_fd

_original_stderr_fd = None
protected

Original file descriptor for stderr before entering the context.

Definition at line 212 of file terminal_utils.py.

◆ _original_stderr_isatty

_original_stderr_isatty = None
protected

Original sys.stderr.isatty.

Definition at line 220 of file terminal_utils.py.

◆ _original_stdout

_original_stdout = None
protected

Original sys.__stdout__ before entering the context.

Definition at line 214 of file terminal_utils.py.

◆ _original_stdout_fd

_original_stdout_fd = None
protected

Original file descriptor for stdout before entering the context.

Definition at line 210 of file terminal_utils.py.

◆ _original_stdout_isatty

_original_stdout_isatty = None
protected

Original sys.stdout.isatty.

Definition at line 218 of file terminal_utils.py.

◆ _pager

str _pager = os.environ.get("PAGER", "less")
protected

pager program to use

Definition at line 198 of file terminal_utils.py.

◆ _pager_process

_pager_process = None
protected

Pager subprocess.

Definition at line 208 of file terminal_utils.py.

◆ _prompt

str _prompt = prompt
protected

prompt string

Definition at line 203 of file terminal_utils.py.

◆ _quit_if_one_screen

_quit_if_one_screen = quit_if_one_screen
protected

flag indicating whether the pager should automatically exit if the content fits on one screen

Definition at line 206 of file terminal_utils.py.


The documentation for this class was generated from the following file: