Skip to content

Commit 580dd44

Browse files
committed
Add file-like pager: click.get_pager_file()
1 parent 24929b0 commit 580dd44

File tree

6 files changed

+44
-4
lines changed

6 files changed

+44
-4
lines changed

CHANGES.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,8 @@ Released 2025-05-10
158158
allows the user to search for future output of the generator when
159159
using less and then aborting the program using ctrl-c.
160160

161+
- Add ``click.get_pager_file`` for file-like access to an output
162+
pager. :pr:`1572`
161163
- ``deprecated: bool | str`` can now be used on options and arguments. This
162164
previously was only available for ``Command``. The message can now also be
163165
customised by using a ``str`` instead of a ``bool``. :issue:`2263` :pr:`2271`

docs/api.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,10 @@ classes and functions.
7272
.. autofunction:: echo_via_pager
7373
```
7474

75+
```{eval-rst}
76+
.. autofunction:: get_pager_file
77+
```
78+
7579
```{eval-rst}
7680
.. autofunction:: prompt
7781
```

docs/utils.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,19 @@ you can pass a generator (or generator function) instead of a string:
110110
click.echo_via_pager(_generate_output())
111111
```
112112

113+
For more complex programs, which can't easily use a simple generator, you
114+
can get access to a writable file-like object for the pager, and write to
115+
that instead:
116+
117+
```{eval-rst}
118+
.. click:example::
119+
@click.command()
120+
def less():
121+
with click.get_pager_file() as pager:
122+
for idx in range(50000):
123+
print(idx, file=pager)
124+
```
125+
113126
## Screen Clearing
114127

115128
```{versionadded} 2.0

src/click/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
from .termui import confirm as confirm
4242
from .termui import echo_via_pager as echo_via_pager
4343
from .termui import edit as edit
44+
from .termui import get_pager_file as get_pager_file
4445
from .termui import getchar as getchar
4546
from .termui import launch as launch
4647
from .termui import pause as pause

src/click/_compat.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -502,6 +502,10 @@ def should_strip_ansi(
502502
if color is None:
503503
if stream is None:
504504
stream = sys.stdin
505+
elif hasattr(stream, "color"):
506+
# ._termui_impl.MaybeStripAnsi handles stripping ansi itself,
507+
# so we don't need to strip it here
508+
return False
505509
return not isatty(stream) and not _is_jupyter_kernel_output(stream)
506510
return not color
507511

src/click/termui.py

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,23 @@ def confirm(
258258
return rv
259259

260260

261+
def get_pager_file(color=None):
262+
"""Context manager.
263+
264+
Yields a writable file-like object which can be used as an output pager.
265+
266+
.. versionadded:: 8.2
267+
268+
:param color: controls if the pager supports ANSI colors or not. The
269+
default is autodetection.
270+
"""
271+
from ._termui_impl import get_pager_file
272+
273+
color = resolve_color_default(color)
274+
275+
return get_pager_file(color=color)
276+
277+
261278
def echo_via_pager(
262279
text_or_generator: cabc.Iterable[str] | t.Callable[[], cabc.Iterable[str]] | str,
263280
color: bool | None = None,
@@ -273,7 +290,6 @@ def echo_via_pager(
273290
:param color: controls if the pager supports ANSI colors or not. The
274291
default is autodetection.
275292
"""
276-
color = resolve_color_default(color)
277293

278294
if inspect.isgeneratorfunction(text_or_generator):
279295
i = t.cast("t.Callable[[], cabc.Iterable[str]]", text_or_generator)()
@@ -285,9 +301,9 @@ def echo_via_pager(
285301
# convert every element of i to a text type if necessary
286302
text_generator = (el if isinstance(el, str) else str(el) for el in i)
287303

288-
from ._termui_impl import pager
289-
290-
return pager(itertools.chain(text_generator, "\n"), color)
304+
with get_pager_file(color=color) as pager:
305+
for text in itertools.chain(text_generator, "\n"):
306+
pager.write(text)
291307

292308

293309
@t.overload

0 commit comments

Comments
 (0)