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/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
- '''Get the current size of your terminal
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
- try:
46
+ with contextlib.suppress(Exception):
16
47
  # Default to 79 characters for IPython notebooks
17
- from IPython import get_ipython
18
- ipython = get_ipython()
19
- from ipykernel import zmqshell
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
- except Exception: # pragma: no cover
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 substract it.
63
+ # safety we'll always subtract it.
33
64
  return w - 1, h
34
- except Exception: # pragma: no cover
35
- pass
36
-
37
- try:
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
- except Exception: # pragma: no cover
43
- pass
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
- except Exception: # pragma: no cover
53
- pass
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
- try:
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
- w, h = _get_terminal_size_windows()
65
- if w and h:
66
- return w, h
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
- try:
92
+ with contextlib.suppress(Exception):
71
93
  # needed for window's python in cygwin's xterm!
72
- w, h = _get_terminal_size_tput()
73
- if w and h:
74
- return w, h
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
- from ctypes import windll, create_string_buffer
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
- (_, _, _, _, _, left, top, right, bottom, _, _) = \
99
- struct.unpack("hhhhHhhhhhh", csbi.raw)
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'], stdin=subprocess.PIPE, stdout=subprocess.PIPE,
113
- stderr=subprocess.PIPE)
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'], stdin=subprocess.PIPE, stdout=subprocess.PIPE,
118
- stderr=subprocess.PIPE)
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
- def _get_terminal_size_linux(): # pragma: no cover
127
- def ioctl_GWINSZ(fd):
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
- size = struct.unpack(
133
- 'hh', fcntl.ioctl(fd, termios.TIOCGWINSZ, '1234'))
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 = ioctl_GWINSZ(0) or ioctl_GWINSZ(1) or ioctl_GWINSZ(2)
199
+ size: _OptionalStrDimensions
200
+ size = ioctl_gwinsz(0) or ioctl_gwinsz(1) or ioctl_gwinsz(2)
139
201
 
140
202
  if not size:
141
- try:
203
+ with contextlib.suppress(Exception):
142
204
  fd = os.open(os.ctermid(), os.O_RDONLY)
143
- size = ioctl_GWINSZ(fd)
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']