python-utils 2.5.6__py3-none-any.whl → 4.0.0__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.
- python_utils/__about__.py +35 -7
- python_utils/__init__.py +241 -0
- python_utils/_aliases.py +53 -0
- python_utils/aio.py +133 -0
- python_utils/containers.py +637 -0
- python_utils/converters.py +265 -85
- python_utils/decorators.py +216 -6
- python_utils/exceptions.py +47 -0
- python_utils/formatters.py +72 -16
- python_utils/generators.py +126 -0
- python_utils/import_.py +64 -26
- python_utils/logger.py +352 -29
- python_utils/loguru.py +53 -0
- python_utils/terminal.py +127 -67
- python_utils/time.py +371 -18
- python_utils/types.py +179 -0
- python_utils-4.0.0.dist-info/METADATA +389 -0
- python_utils-4.0.0.dist-info/RECORD +21 -0
- {python_utils-2.5.6.dist-info → python_utils-4.0.0.dist-info}/WHEEL +1 -3
- python_utils-2.5.6.dist-info/METADATA +0 -122
- python_utils-2.5.6.dist-info/RECORD +0 -15
- python_utils-2.5.6.dist-info/top_level.txt +0 -1
- /python_utils/{compat.py → py.typed} +0 -0
- {python_utils-2.5.6.dist-info → python_utils-4.0.0.dist-info/licenses}/LICENSE +0 -0
python_utils/terminal.py
CHANGED
|
@@ -1,8 +1,37 @@
|
|
|
1
|
+
"""
|
|
2
|
+
This module provides functions to get the terminal size across different
|
|
3
|
+
platforms.
|
|
4
|
+
|
|
5
|
+
Functions:
|
|
6
|
+
get_terminal_size: Get the current size of the terminal.
|
|
7
|
+
_get_terminal_size_windows: Get terminal size on Windows.
|
|
8
|
+
_get_terminal_size_tput: Get terminal size using `tput`.
|
|
9
|
+
_get_terminal_size_linux: Get terminal size on Linux.
|
|
10
|
+
|
|
11
|
+
Usage example:
|
|
12
|
+
>>> width, height = get_terminal_size()
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import contextlib
|
|
1
18
|
import os
|
|
19
|
+
import typing
|
|
20
|
+
|
|
21
|
+
from . import converters
|
|
22
|
+
|
|
23
|
+
#: A terminal size as ``(width, height)`` in character cells.
|
|
24
|
+
Dimensions = tuple[int, int]
|
|
25
|
+
#: A ``Dimensions`` tuple, or ``None`` if the size could not be determined.
|
|
26
|
+
OptionalDimensions = Dimensions | None
|
|
27
|
+
#: A raw terminal size as ``(width, height)`` strings, before ``int`` casting.
|
|
28
|
+
_StrDimensions = tuple[str, str]
|
|
29
|
+
#: A ``_StrDimensions`` tuple, or ``None`` if the size could not be read.
|
|
30
|
+
_OptionalStrDimensions = _StrDimensions | None
|
|
2
31
|
|
|
3
32
|
|
|
4
|
-
def get_terminal_size(): # pragma: no cover
|
|
5
|
-
|
|
33
|
+
def get_terminal_size() -> Dimensions: # pragma: no cover
|
|
34
|
+
"""Get the current size of your terminal.
|
|
6
35
|
|
|
7
36
|
Multiple returns are not always a good idea, but in this case it greatly
|
|
8
37
|
simplifies the code so I believe it's justified. It's not the prettiest
|
|
@@ -10,93 +39,91 @@ def get_terminal_size(): # pragma: no cover
|
|
|
10
39
|
|
|
11
40
|
Returns:
|
|
12
41
|
width, height: Two integers containing width and height
|
|
13
|
-
|
|
42
|
+
"""
|
|
43
|
+
w: int | None
|
|
44
|
+
h: int | None
|
|
14
45
|
|
|
15
|
-
|
|
46
|
+
with contextlib.suppress(Exception):
|
|
16
47
|
# Default to 79 characters for IPython notebooks
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
48
|
+
import IPython # type: ignore[import-not-found]
|
|
49
|
+
|
|
50
|
+
ipython = IPython.get_ipython() # type: ignore[no-untyped-call]
|
|
51
|
+
from ipykernel import zmqshell # type: ignore[import-not-found]
|
|
52
|
+
|
|
20
53
|
if isinstance(ipython, zmqshell.ZMQInteractiveShell):
|
|
21
54
|
return 79, 24
|
|
22
|
-
|
|
23
|
-
pass
|
|
24
|
-
|
|
25
|
-
try:
|
|
55
|
+
with contextlib.suppress(Exception):
|
|
26
56
|
# This works for Python 3, but not Pypy3. Probably the best method if
|
|
27
57
|
# it's supported so let's always try
|
|
28
58
|
import shutil
|
|
59
|
+
|
|
29
60
|
w, h = shutil.get_terminal_size()
|
|
30
61
|
if w and h:
|
|
31
62
|
# The off by one is needed due to progressbars in some cases, for
|
|
32
|
-
# safety we'll always
|
|
63
|
+
# safety we'll always subtract it.
|
|
33
64
|
return w - 1, h
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
w = int(os.environ.get('COLUMNS'))
|
|
39
|
-
h = int(os.environ.get('LINES'))
|
|
65
|
+
with contextlib.suppress(Exception):
|
|
66
|
+
# Fall back to the COLUMNS/LINES environment variables when set.
|
|
67
|
+
w = converters.to_int(os.environ.get('COLUMNS'))
|
|
68
|
+
h = converters.to_int(os.environ.get('LINES'))
|
|
40
69
|
if w and h:
|
|
41
70
|
return w, h
|
|
42
|
-
|
|
43
|
-
|
|
71
|
+
with contextlib.suppress(Exception):
|
|
72
|
+
# Try the optional `blessings` library if it happens to be installed.
|
|
73
|
+
import blessings # type: ignore[import-untyped]
|
|
44
74
|
|
|
45
|
-
try:
|
|
46
|
-
import blessings
|
|
47
75
|
terminal = blessings.Terminal()
|
|
48
76
|
w = terminal.width
|
|
49
77
|
h = terminal.height
|
|
50
78
|
if w and h:
|
|
51
79
|
return w, h
|
|
52
|
-
|
|
53
|
-
|
|
80
|
+
with contextlib.suppress(Exception):
|
|
81
|
+
# The method can return None so we don't unpack it
|
|
82
|
+
wh = _get_terminal_size_linux()
|
|
83
|
+
if wh is not None and all(wh):
|
|
84
|
+
return wh
|
|
54
85
|
|
|
55
|
-
|
|
56
|
-
w, h = _get_terminal_size_linux()
|
|
57
|
-
if w and h:
|
|
58
|
-
return w, h
|
|
59
|
-
except Exception: # pragma: no cover
|
|
60
|
-
pass
|
|
61
|
-
|
|
62
|
-
try:
|
|
86
|
+
with contextlib.suppress(Exception):
|
|
63
87
|
# Windows detection doesn't always work, let's try anyhow
|
|
64
|
-
|
|
65
|
-
if
|
|
66
|
-
return
|
|
67
|
-
except Exception: # pragma: no cover
|
|
68
|
-
pass
|
|
88
|
+
wh = _get_terminal_size_windows()
|
|
89
|
+
if wh is not None and all(wh):
|
|
90
|
+
return wh
|
|
69
91
|
|
|
70
|
-
|
|
92
|
+
with contextlib.suppress(Exception):
|
|
71
93
|
# needed for window's python in cygwin's xterm!
|
|
72
|
-
|
|
73
|
-
if
|
|
74
|
-
return
|
|
75
|
-
except Exception: # pragma: no cover
|
|
76
|
-
pass
|
|
94
|
+
wh = _get_terminal_size_tput()
|
|
95
|
+
if wh is not None and all(wh):
|
|
96
|
+
return wh
|
|
77
97
|
|
|
78
98
|
return 79, 24
|
|
79
99
|
|
|
80
100
|
|
|
81
|
-
def _get_terminal_size_windows(): # pragma: no cover
|
|
101
|
+
def _get_terminal_size_windows() -> OptionalDimensions: # pragma: no cover
|
|
102
|
+
"""Return the terminal size on Windows via the Win32 console API.
|
|
103
|
+
|
|
104
|
+
Returns:
|
|
105
|
+
The ``(width, height)`` in cells, or ``None`` if it cannot be read.
|
|
106
|
+
"""
|
|
82
107
|
res = None
|
|
83
108
|
try:
|
|
84
|
-
|
|
109
|
+
import ctypes
|
|
85
110
|
|
|
86
111
|
# stdin handle is -10
|
|
87
112
|
# stdout handle is -11
|
|
88
113
|
# stderr handle is -12
|
|
89
114
|
|
|
90
|
-
h = windll.kernel32.GetStdHandle(-12)
|
|
91
|
-
csbi = create_string_buffer(22)
|
|
92
|
-
res = windll.kernel32.GetConsoleScreenBufferInfo(h, csbi)
|
|
115
|
+
h = ctypes.windll.kernel32.GetStdHandle(-12) # type: ignore[attr-defined]
|
|
116
|
+
csbi = ctypes.create_string_buffer(22)
|
|
117
|
+
res = ctypes.windll.kernel32.GetConsoleScreenBufferInfo(h, csbi) # type: ignore[attr-defined]
|
|
93
118
|
except Exception:
|
|
94
119
|
return None
|
|
95
120
|
|
|
96
121
|
if res:
|
|
97
122
|
import struct
|
|
98
|
-
|
|
99
|
-
|
|
123
|
+
|
|
124
|
+
(_, _, _, _, _, left, top, right, bottom, _, _) = struct.unpack(
|
|
125
|
+
'hhhhHhhhhhh', csbi.raw
|
|
126
|
+
)
|
|
100
127
|
w = right - left
|
|
101
128
|
h = bottom - top
|
|
102
129
|
return w, h
|
|
@@ -104,46 +131,79 @@ def _get_terminal_size_windows(): # pragma: no cover
|
|
|
104
131
|
return None
|
|
105
132
|
|
|
106
133
|
|
|
107
|
-
def _get_terminal_size_tput(): # pragma: no cover
|
|
134
|
+
def _get_terminal_size_tput() -> OptionalDimensions: # pragma: no cover
|
|
135
|
+
"""Return the terminal size by shelling out to ``tput``.
|
|
136
|
+
|
|
137
|
+
This is mainly needed for Windows Python running under Cygwin's xterm.
|
|
138
|
+
|
|
139
|
+
Returns:
|
|
140
|
+
The ``(width, height)`` in cells, or ``None`` on any failure.
|
|
141
|
+
"""
|
|
108
142
|
# get terminal width src: http://stackoverflow.com/questions/263890/
|
|
109
143
|
try:
|
|
110
144
|
import subprocess
|
|
145
|
+
|
|
111
146
|
proc = subprocess.Popen(
|
|
112
|
-
['tput', 'cols'],
|
|
113
|
-
|
|
147
|
+
['tput', 'cols'],
|
|
148
|
+
stdin=subprocess.PIPE,
|
|
149
|
+
stdout=subprocess.PIPE,
|
|
150
|
+
stderr=subprocess.PIPE,
|
|
151
|
+
)
|
|
114
152
|
output = proc.communicate(input=None)
|
|
115
153
|
w = int(output[0])
|
|
116
154
|
proc = subprocess.Popen(
|
|
117
|
-
['tput', 'lines'],
|
|
118
|
-
|
|
155
|
+
['tput', 'lines'],
|
|
156
|
+
stdin=subprocess.PIPE,
|
|
157
|
+
stdout=subprocess.PIPE,
|
|
158
|
+
stderr=subprocess.PIPE,
|
|
159
|
+
)
|
|
119
160
|
output = proc.communicate(input=None)
|
|
120
161
|
h = int(output[0])
|
|
121
|
-
return w, h
|
|
122
162
|
except Exception:
|
|
123
163
|
return None
|
|
164
|
+
else:
|
|
165
|
+
return w, h
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def _get_terminal_size_linux() -> OptionalDimensions: # pragma: no cover
|
|
169
|
+
"""Return the terminal size on Unix-likes via ``ioctl`` or the environment.
|
|
170
|
+
|
|
171
|
+
Tries ``TIOCGWINSZ`` on the standard file descriptors, then the controlling
|
|
172
|
+
terminal, then the ``LINES``/``COLUMNS`` environment variables.
|
|
173
|
+
|
|
174
|
+
Returns:
|
|
175
|
+
The ``(width, height)`` in cells, or ``None`` if every method fails.
|
|
176
|
+
"""
|
|
124
177
|
|
|
178
|
+
def ioctl_gwinsz(fd: int) -> tuple[str, str] | None:
|
|
179
|
+
"""Query ``fd`` for its window size via the ``TIOCGWINSZ`` ioctl.
|
|
125
180
|
|
|
126
|
-
|
|
127
|
-
|
|
181
|
+
Returns:
|
|
182
|
+
The ``(rows, cols)`` window size, or ``None`` if the ioctl fails.
|
|
183
|
+
"""
|
|
128
184
|
try:
|
|
129
185
|
import fcntl
|
|
130
|
-
import termios
|
|
131
186
|
import struct
|
|
132
|
-
|
|
133
|
-
|
|
187
|
+
import termios
|
|
188
|
+
|
|
189
|
+
return typing.cast(
|
|
190
|
+
_OptionalStrDimensions,
|
|
191
|
+
struct.unpack(
|
|
192
|
+
'hh',
|
|
193
|
+
fcntl.ioctl(fd, termios.TIOCGWINSZ, '1234'), # type: ignore[call-overload]
|
|
194
|
+
),
|
|
195
|
+
)
|
|
134
196
|
except Exception:
|
|
135
197
|
return None
|
|
136
|
-
return size
|
|
137
198
|
|
|
138
|
-
size
|
|
199
|
+
size: _OptionalStrDimensions
|
|
200
|
+
size = ioctl_gwinsz(0) or ioctl_gwinsz(1) or ioctl_gwinsz(2)
|
|
139
201
|
|
|
140
202
|
if not size:
|
|
141
|
-
|
|
203
|
+
with contextlib.suppress(Exception):
|
|
142
204
|
fd = os.open(os.ctermid(), os.O_RDONLY)
|
|
143
|
-
size =
|
|
205
|
+
size = ioctl_gwinsz(fd)
|
|
144
206
|
os.close(fd)
|
|
145
|
-
except Exception:
|
|
146
|
-
pass
|
|
147
207
|
if not size:
|
|
148
208
|
try:
|
|
149
209
|
size = os.environ['LINES'], os.environ['COLUMNS']
|