omlish 0.0.0.dev470__py3-none-any.whl → 0.0.0.dev472__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
omlish/term/pager.py ADDED
@@ -0,0 +1,235 @@
1
+ # ruff: noqa: S602 S605
2
+ #
3
+ # Copyright 2000-2008 Michael Hudson-Doyle <micahel@gmail.com>
4
+ # Armin Rigo
5
+ #
6
+ # All Rights Reserved
7
+ #
8
+ #
9
+ # Permission to use, copy, modify, and distribute this software and its documentation for any purpose is hereby granted
10
+ # without fee, provided that the above copyright notice appear in all copies and that both that copyright notice and
11
+ # this permission notice appear in supporting documentation.
12
+ #
13
+ # THE AUTHOR MICHAEL HUDSON DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF
14
+ # MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES
15
+ # OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
16
+ # OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17
+ #
18
+ # https://github.com/python/cpython/tree/bcced02604f845b2b71d0a1dd95f95366bd7774d/Lib/_pyrepl
19
+ import io
20
+ import os
21
+ import re
22
+ import subprocess
23
+ import sys
24
+ import tempfile
25
+ import termios
26
+ import tty
27
+ import typing as ta
28
+
29
+ from .. import check
30
+
31
+
32
+ ##
33
+
34
+
35
+ class Pager(ta.Protocol):
36
+ def __call__(self, text: str, title: str = '') -> None:
37
+ ...
38
+
39
+
40
+ def get_pager() -> Pager:
41
+ """Decide what method to use for paging through text."""
42
+
43
+ if not hasattr(sys.stdin, 'isatty'):
44
+ return plain_pager
45
+
46
+ if not hasattr(sys.stdout, 'isatty'):
47
+ return plain_pager
48
+
49
+ if not sys.stdin.isatty() or not sys.stdout.isatty():
50
+ return plain_pager
51
+
52
+ if sys.platform == 'emscripten':
53
+ return plain_pager
54
+
55
+ use_pager = os.environ.get('MANPAGER') or os.environ.get('PAGER')
56
+ if use_pager:
57
+ if sys.platform == 'win32': # pipes completely broken in Windows
58
+ raise OSError
59
+
60
+ elif os.environ.get('TERM') in ('dumb', 'emacs'):
61
+ return lambda text, title='': pipe_pager(plain(text), use_pager, title)
62
+
63
+ else:
64
+ return lambda text, title='': pipe_pager(text, use_pager, title)
65
+
66
+ if os.environ.get('TERM') in ('dumb', 'emacs'):
67
+ return plain_pager
68
+
69
+ if sys.platform == 'win32':
70
+ return lambda text, title='': tempfile_pager(plain(text), 'more <')
71
+
72
+ if hasattr(os, 'system') and os.system('(pager) 2>/dev/null') == 0:
73
+ return lambda text, title='': pipe_pager(text, 'pager', title)
74
+
75
+ if hasattr(os, 'system') and os.system('(less) 2>/dev/null') == 0:
76
+ return lambda text, title='': pipe_pager(text, 'less', title)
77
+
78
+ import tempfile
79
+ (fd, filename) = tempfile.mkstemp()
80
+ os.close(fd)
81
+ try:
82
+ if hasattr(os, 'system') and os.system(f'more "{filename}"') == 0:
83
+ return lambda text, title='': pipe_pager(text, 'more', title)
84
+
85
+ else:
86
+ return tty_pager
87
+
88
+ finally:
89
+ os.unlink(filename)
90
+
91
+
92
+ def escape_stdout(text: str) -> str:
93
+ # Escape non-encodable characters to avoid encoding errors later
94
+ encoding = getattr(sys.stdout, 'encoding', None) or 'utf-8'
95
+ return text.encode(encoding, 'backslashreplace').decode(encoding)
96
+
97
+
98
+ def escape_less(s: str) -> str:
99
+ return re.sub(r'([?:.%\\])', r'\\\1', s)
100
+
101
+
102
+ def plain(text: str) -> str:
103
+ """Remove boldface formatting from text."""
104
+
105
+ return re.sub('.\b', '', text)
106
+
107
+
108
+ def tty_pager(text: str, title: str = '') -> None:
109
+ """Page through text on a text terminal."""
110
+
111
+ lines = plain(escape_stdout(text)).split('\n')
112
+ has_tty = False
113
+ try:
114
+ fd = sys.stdin.fileno()
115
+ old = termios.tcgetattr(fd)
116
+ tty.setcbreak(fd)
117
+ has_tty = True
118
+
119
+ def getchar() -> str:
120
+ return sys.stdin.read(1)
121
+
122
+ except (ImportError, AttributeError, io.UnsupportedOperation):
123
+ def getchar() -> str:
124
+ return sys.stdin.readline()[:-1][:1]
125
+
126
+ try:
127
+ try:
128
+ h = int(os.environ.get('LINES', '0'))
129
+ except ValueError:
130
+ h = 0
131
+ if h <= 1:
132
+ h = 25
133
+ r = inc = h - 1
134
+
135
+ sys.stdout.write('\n'.join(lines[:inc]) + '\n')
136
+ while lines[r:]:
137
+ sys.stdout.write('-- more --')
138
+ sys.stdout.flush()
139
+ c = getchar()
140
+
141
+ if c in ('q', 'Q'):
142
+ sys.stdout.write('\r \r')
143
+ break
144
+
145
+ elif c in ('\r', '\n'):
146
+ sys.stdout.write('\r \r' + lines[r] + '\n')
147
+ r = r + 1
148
+ continue
149
+
150
+ if c in ('b', 'B', '\x1b'):
151
+ r = r - inc - inc
152
+ if r < 0:
153
+ r = 0
154
+
155
+ sys.stdout.write('\n' + '\n'.join(lines[r:r + inc]) + '\n')
156
+ r = r + inc
157
+
158
+ finally:
159
+ if has_tty:
160
+ termios.tcsetattr(fd, termios.TCSAFLUSH, old) # noqa
161
+
162
+
163
+ def plain_pager(text: str, title: str = '') -> None:
164
+ """Simply print unformatted text. This is the ultimate fallback."""
165
+
166
+ sys.stdout.write(plain(escape_stdout(text)))
167
+
168
+
169
+ def pipe_pager(text: str, cmd: str, title: str = '') -> None:
170
+ """Page through text by feeding it to another program."""
171
+
172
+ env = os.environ.copy()
173
+
174
+ if title:
175
+ title += ' '
176
+ esc_title = escape_less(title)
177
+
178
+ prompt_string = (
179
+ f' {esc_title}'
180
+ '?ltline %lt?L/%L.'
181
+ ':byte %bB?s/%s.'
182
+ '.'
183
+ '?e (END):?pB %pB\\%..'
184
+ ' (press h for help or q to quit)'
185
+ )
186
+
187
+ env['LESS'] = f'-RmPm{prompt_string}$PM{prompt_string}$'
188
+
189
+ proc = subprocess.Popen(
190
+ cmd,
191
+ shell=True,
192
+ stdin=subprocess.PIPE,
193
+ errors='backslashreplace',
194
+ env=env,
195
+ )
196
+
197
+ try:
198
+ with check.not_none(proc.stdin) as pipe:
199
+ try:
200
+ pipe.write(text)
201
+
202
+ except KeyboardInterrupt:
203
+ # We've hereby abandoned whatever text hasn't been written, but the pager is still in control of the
204
+ # terminal.
205
+ pass
206
+
207
+ except OSError:
208
+ pass # Ignore broken pipes caused by quitting the pager program.
209
+
210
+ while True:
211
+ try:
212
+ proc.wait()
213
+ break
214
+
215
+ except KeyboardInterrupt:
216
+ # Ignore ctl-c like the pager itself does. Otherwise the pager is left running and the terminal is in raw
217
+ # mode and unusable.
218
+ pass
219
+
220
+
221
+ def tempfile_pager(text: str, cmd: str, title: str = '') -> None:
222
+ """Page through text by invoking a program on a temporary file."""
223
+
224
+ with tempfile.TemporaryDirectory() as tempdir:
225
+ filename = os.path.join(tempdir, 'pydoc.out')
226
+
227
+ with open(
228
+ filename,
229
+ 'w',
230
+ errors='backslashreplace',
231
+ encoding=os.device_encoding(0) if sys.platform == 'win32' else None,
232
+ ) as file:
233
+ file.write(text)
234
+
235
+ os.system(cmd + ' "' + filename + '"')