88
99import collections .abc as cabc
1010import contextlib
11+ import io
1112import math
1213import os
1314import sys
@@ -363,7 +364,20 @@ def generator(self) -> cabc.Iterator[V]:
363364 self .render_progress ()
364365
365366
366- def _pager_contextmanager (color : bool | None = None ):
367+ class MaybeStripAnsi (io .TextIOWrapper ):
368+ def __init__ (self , stream : t .IO [bytes ], * , color : bool , ** kwargs : t .Any ):
369+ super ().__init__ (stream , ** kwargs )
370+ self .color = color
371+
372+ def write (self , text : str ) -> int :
373+ if not self .color :
374+ text = strip_ansi (text )
375+ return super ().write (text )
376+
377+
378+ def _pager_contextmanager (
379+ color : bool | None = None ,
380+ ) -> t .ContextManager [t .Tuple [t .BinaryIO , str , bool ]]:
367381 """Decide what method to use for paging through text."""
368382 stdout = _default_text_stdout ()
369383
@@ -398,17 +412,26 @@ def _pager_contextmanager(color: bool | None = None):
398412 os .unlink (filename )
399413
400414
401- def pager (generator : cabc .Iterable [str ], color : bool | None = None ):
402- """Given an iterable of text, write it all to an output pager."""
403- with _pager_contextmanager (color = color ) as (pager_file , encoding , color ):
404- for text in generator :
405- if not color :
406- text = strip_ansi (text )
407- pager_file .write (text .encode (encoding , "replace" ))
415+ @contextlib .contextmanager
416+ def get_pager_file (color : bool | None = None ) -> t .Generator [t .IO , None , None ]:
417+ """Context manager.
418+ Yields a writable file-like object which can be used as an output pager.
419+ .. versionadded:: 8.2
420+ :param color: controls if the pager supports ANSI colors or not. The
421+ default is autodetection.
422+ """
423+ with _pager_contextmanager (color = color ) as (stream , encoding , color ):
424+ if not getattr (stream , "encoding" , None ):
425+ # wrap in a text stream
426+ stream = MaybeStripAnsi (stream , color = color , encoding = encoding )
427+ yield stream
428+ stream .flush ()
408429
409430
410431@contextlib .contextmanager
411- def _pipepager (cmd : str , color : bool | None = None ):
432+ def _pipepager (
433+ cmd : str , color : bool | None
434+ ) -> t .Iterator [t .Tuple [t .BinaryIO , str , bool ]]:
412435 """Page through text by feeding it to another program. Invoking a
413436 pager through this might support colors.
414437 """
@@ -427,6 +450,9 @@ def _pipepager(cmd: str, color: bool | None = None):
427450 elif "r" in less_flags or "R" in less_flags :
428451 color = True
429452
453+ if color is None :
454+ color = False
455+
430456 c = subprocess .Popen (cmd , shell = True , stdin = subprocess .PIPE , env = env )
431457 stdin = t .cast (t .BinaryIO , c .stdin )
432458 encoding = get_best_encoding (stdin )
@@ -469,7 +495,9 @@ def _pipepager(cmd: str, color: bool | None = None):
469495
470496
471497@contextlib .contextmanager
472- def _tempfilepager (cmd : str , color : bool | None = None ):
498+ def _tempfilepager (
499+ cmd : str , color : bool | None = None
500+ ) -> t .Iterator [t .Tuple [t .BinaryIO , str , bool ]]:
473501 """Page through text by invoking a program on a temporary file."""
474502 import tempfile
475503
@@ -481,10 +509,12 @@ def _tempfilepager(cmd: str, color: bool | None = None):
481509
482510
483511@contextlib .contextmanager
484- def _nullpager (stream : t .TextIO , color : bool | None = None ):
512+ def _nullpager (
513+ stream : t .TextIO , color : bool | None = None
514+ ) -> t .Iterator [t .Tuple [t .BinaryIO , str , bool ]]:
485515 """Simply print unformatted text. This is the ultimate fallback."""
486516 encoding = get_best_encoding (stream )
487- return stream , encoding , color
517+ yield stream , encoding , color
488518
489519
490520class Editor :
@@ -629,23 +659,23 @@ def _unquote_file(url: str) -> str:
629659 wait_str = "-w" if wait else ""
630660 args = f'cygstart { wait_str } "{ url } "'
631661 return os .system (args )
632-
633- try :
634- if locate :
635- url = os .path .dirname (_unquote_file (url )) or "."
636- else :
637- url = _unquote_file (url )
638- c = subprocess .Popen (["xdg-open" , url ])
639- if wait :
640- return c .wait ()
641- return 0
642- except OSError :
643- if url .startswith (("http://" , "https://" )) and not locate and not wait :
644- import webbrowser
645-
646- webbrowser .open (url )
662+ else :
663+ try :
664+ if locate :
665+ url = os .path .dirname (_unquote_file (url )) or "."
666+ else :
667+ url = _unquote_file (url )
668+ c = subprocess .Popen (["xdg-open" , url ])
669+ if wait :
670+ return c .wait ()
647671 return 0
648- return 1
672+ except OSError :
673+ if url .startswith (("http://" , "https://" )) and not locate and not wait :
674+ import webbrowser
675+
676+ webbrowser .open (url )
677+ return 0
678+ return 1
649679
650680
651681def _translate_ch_to_exc (ch : str ) -> None :
0 commit comments