ZhengyangZhang commited on
Commit
72f5621
·
verified ·
1 Parent(s): c4a2245

Add files using upload-large-folder tool

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. lib/python3.12/site-packages/click/__init__.py +123 -0
  2. lib/python3.12/site-packages/click/__pycache__/__init__.cpython-312.pyc +0 -0
  3. lib/python3.12/site-packages/click/__pycache__/_compat.cpython-312.pyc +0 -0
  4. lib/python3.12/site-packages/click/__pycache__/_termui_impl.cpython-312.pyc +0 -0
  5. lib/python3.12/site-packages/click/__pycache__/_textwrap.cpython-312.pyc +0 -0
  6. lib/python3.12/site-packages/click/__pycache__/_winconsole.cpython-312.pyc +0 -0
  7. lib/python3.12/site-packages/click/__pycache__/decorators.cpython-312.pyc +0 -0
  8. lib/python3.12/site-packages/click/__pycache__/exceptions.cpython-312.pyc +0 -0
  9. lib/python3.12/site-packages/click/__pycache__/formatting.cpython-312.pyc +0 -0
  10. lib/python3.12/site-packages/click/__pycache__/globals.cpython-312.pyc +0 -0
  11. lib/python3.12/site-packages/click/__pycache__/parser.cpython-312.pyc +0 -0
  12. lib/python3.12/site-packages/click/__pycache__/shell_completion.cpython-312.pyc +0 -0
  13. lib/python3.12/site-packages/click/__pycache__/termui.cpython-312.pyc +0 -0
  14. lib/python3.12/site-packages/click/__pycache__/testing.cpython-312.pyc +0 -0
  15. lib/python3.12/site-packages/click/__pycache__/types.cpython-312.pyc +0 -0
  16. lib/python3.12/site-packages/click/__pycache__/utils.cpython-312.pyc +0 -0
  17. lib/python3.12/site-packages/click/_compat.py +622 -0
  18. lib/python3.12/site-packages/click/_termui_impl.py +839 -0
  19. lib/python3.12/site-packages/click/_textwrap.py +51 -0
  20. lib/python3.12/site-packages/click/_winconsole.py +296 -0
  21. lib/python3.12/site-packages/click/core.py +0 -0
  22. lib/python3.12/site-packages/click/decorators.py +551 -0
  23. lib/python3.12/site-packages/click/exceptions.py +308 -0
  24. lib/python3.12/site-packages/click/formatting.py +301 -0
  25. lib/python3.12/site-packages/click/globals.py +67 -0
  26. lib/python3.12/site-packages/click/parser.py +532 -0
  27. lib/python3.12/site-packages/click/py.typed +0 -0
  28. lib/python3.12/site-packages/click/shell_completion.py +644 -0
  29. lib/python3.12/site-packages/click/termui.py +877 -0
  30. lib/python3.12/site-packages/click/testing.py +565 -0
  31. lib/python3.12/site-packages/click/types.py +1165 -0
  32. lib/python3.12/site-packages/click/utils.py +627 -0
  33. lib/python3.12/site-packages/contourpy/__init__.py +285 -0
  34. lib/python3.12/site-packages/contourpy/__pycache__/__init__.cpython-312.pyc +0 -0
  35. lib/python3.12/site-packages/contourpy/__pycache__/_version.cpython-312.pyc +0 -0
  36. lib/python3.12/site-packages/contourpy/__pycache__/array.cpython-312.pyc +0 -0
  37. lib/python3.12/site-packages/contourpy/__pycache__/chunk.cpython-312.pyc +0 -0
  38. lib/python3.12/site-packages/contourpy/__pycache__/convert.cpython-312.pyc +0 -0
  39. lib/python3.12/site-packages/contourpy/__pycache__/dechunk.cpython-312.pyc +0 -0
  40. lib/python3.12/site-packages/contourpy/__pycache__/enum_util.cpython-312.pyc +0 -0
  41. lib/python3.12/site-packages/contourpy/__pycache__/typecheck.cpython-312.pyc +0 -0
  42. lib/python3.12/site-packages/contourpy/__pycache__/types.cpython-312.pyc +0 -0
  43. lib/python3.12/site-packages/contourpy/_contourpy.pyi +199 -0
  44. lib/python3.12/site-packages/contourpy/_version.py +1 -0
  45. lib/python3.12/site-packages/contourpy/array.py +261 -0
  46. lib/python3.12/site-packages/contourpy/chunk.py +95 -0
  47. lib/python3.12/site-packages/contourpy/convert.py +621 -0
  48. lib/python3.12/site-packages/contourpy/dechunk.py +207 -0
  49. lib/python3.12/site-packages/contourpy/enum_util.py +57 -0
  50. lib/python3.12/site-packages/contourpy/py.typed +0 -0
lib/python3.12/site-packages/click/__init__.py ADDED
@@ -0,0 +1,123 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Click is a simple Python module inspired by the stdlib optparse to make
3
+ writing command line scripts fun. Unlike other modules, it's based
4
+ around a simple API that does not come with too much magic and is
5
+ composable.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from .core import Argument as Argument
11
+ from .core import Command as Command
12
+ from .core import CommandCollection as CommandCollection
13
+ from .core import Context as Context
14
+ from .core import Group as Group
15
+ from .core import Option as Option
16
+ from .core import Parameter as Parameter
17
+ from .decorators import argument as argument
18
+ from .decorators import command as command
19
+ from .decorators import confirmation_option as confirmation_option
20
+ from .decorators import group as group
21
+ from .decorators import help_option as help_option
22
+ from .decorators import make_pass_decorator as make_pass_decorator
23
+ from .decorators import option as option
24
+ from .decorators import pass_context as pass_context
25
+ from .decorators import pass_obj as pass_obj
26
+ from .decorators import password_option as password_option
27
+ from .decorators import version_option as version_option
28
+ from .exceptions import Abort as Abort
29
+ from .exceptions import BadArgumentUsage as BadArgumentUsage
30
+ from .exceptions import BadOptionUsage as BadOptionUsage
31
+ from .exceptions import BadParameter as BadParameter
32
+ from .exceptions import ClickException as ClickException
33
+ from .exceptions import FileError as FileError
34
+ from .exceptions import MissingParameter as MissingParameter
35
+ from .exceptions import NoSuchOption as NoSuchOption
36
+ from .exceptions import UsageError as UsageError
37
+ from .formatting import HelpFormatter as HelpFormatter
38
+ from .formatting import wrap_text as wrap_text
39
+ from .globals import get_current_context as get_current_context
40
+ from .termui import clear as clear
41
+ from .termui import confirm as confirm
42
+ from .termui import echo_via_pager as echo_via_pager
43
+ from .termui import edit as edit
44
+ from .termui import getchar as getchar
45
+ from .termui import launch as launch
46
+ from .termui import pause as pause
47
+ from .termui import progressbar as progressbar
48
+ from .termui import prompt as prompt
49
+ from .termui import secho as secho
50
+ from .termui import style as style
51
+ from .termui import unstyle as unstyle
52
+ from .types import BOOL as BOOL
53
+ from .types import Choice as Choice
54
+ from .types import DateTime as DateTime
55
+ from .types import File as File
56
+ from .types import FLOAT as FLOAT
57
+ from .types import FloatRange as FloatRange
58
+ from .types import INT as INT
59
+ from .types import IntRange as IntRange
60
+ from .types import ParamType as ParamType
61
+ from .types import Path as Path
62
+ from .types import STRING as STRING
63
+ from .types import Tuple as Tuple
64
+ from .types import UNPROCESSED as UNPROCESSED
65
+ from .types import UUID as UUID
66
+ from .utils import echo as echo
67
+ from .utils import format_filename as format_filename
68
+ from .utils import get_app_dir as get_app_dir
69
+ from .utils import get_binary_stream as get_binary_stream
70
+ from .utils import get_text_stream as get_text_stream
71
+ from .utils import open_file as open_file
72
+
73
+
74
+ def __getattr__(name: str) -> object:
75
+ import warnings
76
+
77
+ if name == "BaseCommand":
78
+ from .core import _BaseCommand
79
+
80
+ warnings.warn(
81
+ "'BaseCommand' is deprecated and will be removed in Click 9.0. Use"
82
+ " 'Command' instead.",
83
+ DeprecationWarning,
84
+ stacklevel=2,
85
+ )
86
+ return _BaseCommand
87
+
88
+ if name == "MultiCommand":
89
+ from .core import _MultiCommand
90
+
91
+ warnings.warn(
92
+ "'MultiCommand' is deprecated and will be removed in Click 9.0. Use"
93
+ " 'Group' instead.",
94
+ DeprecationWarning,
95
+ stacklevel=2,
96
+ )
97
+ return _MultiCommand
98
+
99
+ if name == "OptionParser":
100
+ from .parser import _OptionParser
101
+
102
+ warnings.warn(
103
+ "'OptionParser' is deprecated and will be removed in Click 9.0. The"
104
+ " old parser is available in 'optparse'.",
105
+ DeprecationWarning,
106
+ stacklevel=2,
107
+ )
108
+ return _OptionParser
109
+
110
+ if name == "__version__":
111
+ import importlib.metadata
112
+ import warnings
113
+
114
+ warnings.warn(
115
+ "The '__version__' attribute is deprecated and will be removed in"
116
+ " Click 9.1. Use feature detection or"
117
+ " 'importlib.metadata.version(\"click\")' instead.",
118
+ DeprecationWarning,
119
+ stacklevel=2,
120
+ )
121
+ return importlib.metadata.version("click")
122
+
123
+ raise AttributeError(name)
lib/python3.12/site-packages/click/__pycache__/__init__.cpython-312.pyc ADDED
Binary file (4.07 kB). View file
 
lib/python3.12/site-packages/click/__pycache__/_compat.cpython-312.pyc ADDED
Binary file (24.2 kB). View file
 
lib/python3.12/site-packages/click/__pycache__/_termui_impl.cpython-312.pyc ADDED
Binary file (31.5 kB). View file
 
lib/python3.12/site-packages/click/__pycache__/_textwrap.cpython-312.pyc ADDED
Binary file (2.42 kB). View file
 
lib/python3.12/site-packages/click/__pycache__/_winconsole.cpython-312.pyc ADDED
Binary file (11.8 kB). View file
 
lib/python3.12/site-packages/click/__pycache__/decorators.cpython-312.pyc ADDED
Binary file (22.1 kB). View file
 
lib/python3.12/site-packages/click/__pycache__/exceptions.cpython-312.pyc ADDED
Binary file (14.8 kB). View file
 
lib/python3.12/site-packages/click/__pycache__/formatting.cpython-312.pyc ADDED
Binary file (13.7 kB). View file
 
lib/python3.12/site-packages/click/__pycache__/globals.cpython-312.pyc ADDED
Binary file (2.96 kB). View file
 
lib/python3.12/site-packages/click/__pycache__/parser.cpython-312.pyc ADDED
Binary file (20.2 kB). View file
 
lib/python3.12/site-packages/click/__pycache__/shell_completion.cpython-312.pyc ADDED
Binary file (23.2 kB). View file
 
lib/python3.12/site-packages/click/__pycache__/termui.cpython-312.pyc ADDED
Binary file (34.5 kB). View file
 
lib/python3.12/site-packages/click/__pycache__/testing.cpython-312.pyc ADDED
Binary file (26.9 kB). View file
 
lib/python3.12/site-packages/click/__pycache__/types.cpython-312.pyc ADDED
Binary file (49 kB). View file
 
lib/python3.12/site-packages/click/__pycache__/utils.cpython-312.pyc ADDED
Binary file (24.9 kB). View file
 
lib/python3.12/site-packages/click/_compat.py ADDED
@@ -0,0 +1,622 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import codecs
4
+ import collections.abc as cabc
5
+ import io
6
+ import os
7
+ import re
8
+ import sys
9
+ import typing as t
10
+ from types import TracebackType
11
+ from weakref import WeakKeyDictionary
12
+
13
+ CYGWIN = sys.platform.startswith("cygwin")
14
+ WIN = sys.platform.startswith("win")
15
+ auto_wrap_for_ansi: t.Callable[[t.TextIO], t.TextIO] | None = None
16
+ _ansi_re = re.compile(r"\033\[[;?0-9]*[a-zA-Z]")
17
+
18
+
19
+ def _make_text_stream(
20
+ stream: t.BinaryIO,
21
+ encoding: str | None,
22
+ errors: str | None,
23
+ force_readable: bool = False,
24
+ force_writable: bool = False,
25
+ ) -> t.TextIO:
26
+ if encoding is None:
27
+ encoding = get_best_encoding(stream)
28
+ if errors is None:
29
+ errors = "replace"
30
+ return _NonClosingTextIOWrapper(
31
+ stream,
32
+ encoding,
33
+ errors,
34
+ line_buffering=True,
35
+ force_readable=force_readable,
36
+ force_writable=force_writable,
37
+ )
38
+
39
+
40
+ def is_ascii_encoding(encoding: str) -> bool:
41
+ """Checks if a given encoding is ascii."""
42
+ try:
43
+ return codecs.lookup(encoding).name == "ascii"
44
+ except LookupError:
45
+ return False
46
+
47
+
48
+ def get_best_encoding(stream: t.IO[t.Any]) -> str:
49
+ """Returns the default stream encoding if not found."""
50
+ rv = getattr(stream, "encoding", None) or sys.getdefaultencoding()
51
+ if is_ascii_encoding(rv):
52
+ return "utf-8"
53
+ return rv
54
+
55
+
56
+ class _NonClosingTextIOWrapper(io.TextIOWrapper):
57
+ def __init__(
58
+ self,
59
+ stream: t.BinaryIO,
60
+ encoding: str | None,
61
+ errors: str | None,
62
+ force_readable: bool = False,
63
+ force_writable: bool = False,
64
+ **extra: t.Any,
65
+ ) -> None:
66
+ self._stream = stream = t.cast(
67
+ t.BinaryIO, _FixupStream(stream, force_readable, force_writable)
68
+ )
69
+ super().__init__(stream, encoding, errors, **extra)
70
+
71
+ def __del__(self) -> None:
72
+ try:
73
+ self.detach()
74
+ except Exception:
75
+ pass
76
+
77
+ def isatty(self) -> bool:
78
+ # https://bitbucket.org/pypy/pypy/issue/1803
79
+ return self._stream.isatty()
80
+
81
+
82
+ class _FixupStream:
83
+ """The new io interface needs more from streams than streams
84
+ traditionally implement. As such, this fix-up code is necessary in
85
+ some circumstances.
86
+
87
+ The forcing of readable and writable flags are there because some tools
88
+ put badly patched objects on sys (one such offender are certain version
89
+ of jupyter notebook).
90
+ """
91
+
92
+ def __init__(
93
+ self,
94
+ stream: t.BinaryIO,
95
+ force_readable: bool = False,
96
+ force_writable: bool = False,
97
+ ):
98
+ self._stream = stream
99
+ self._force_readable = force_readable
100
+ self._force_writable = force_writable
101
+
102
+ def __getattr__(self, name: str) -> t.Any:
103
+ return getattr(self._stream, name)
104
+
105
+ def read1(self, size: int) -> bytes:
106
+ f = getattr(self._stream, "read1", None)
107
+
108
+ if f is not None:
109
+ return t.cast(bytes, f(size))
110
+
111
+ return self._stream.read(size)
112
+
113
+ def readable(self) -> bool:
114
+ if self._force_readable:
115
+ return True
116
+ x = getattr(self._stream, "readable", None)
117
+ if x is not None:
118
+ return t.cast(bool, x())
119
+ try:
120
+ self._stream.read(0)
121
+ except Exception:
122
+ return False
123
+ return True
124
+
125
+ def writable(self) -> bool:
126
+ if self._force_writable:
127
+ return True
128
+ x = getattr(self._stream, "writable", None)
129
+ if x is not None:
130
+ return t.cast(bool, x())
131
+ try:
132
+ self._stream.write(b"")
133
+ except Exception:
134
+ try:
135
+ self._stream.write(b"")
136
+ except Exception:
137
+ return False
138
+ return True
139
+
140
+ def seekable(self) -> bool:
141
+ x = getattr(self._stream, "seekable", None)
142
+ if x is not None:
143
+ return t.cast(bool, x())
144
+ try:
145
+ self._stream.seek(self._stream.tell())
146
+ except Exception:
147
+ return False
148
+ return True
149
+
150
+
151
+ def _is_binary_reader(stream: t.IO[t.Any], default: bool = False) -> bool:
152
+ try:
153
+ return isinstance(stream.read(0), bytes)
154
+ except Exception:
155
+ return default
156
+ # This happens in some cases where the stream was already
157
+ # closed. In this case, we assume the default.
158
+
159
+
160
+ def _is_binary_writer(stream: t.IO[t.Any], default: bool = False) -> bool:
161
+ try:
162
+ stream.write(b"")
163
+ except Exception:
164
+ try:
165
+ stream.write("")
166
+ return False
167
+ except Exception:
168
+ pass
169
+ return default
170
+ return True
171
+
172
+
173
+ def _find_binary_reader(stream: t.IO[t.Any]) -> t.BinaryIO | None:
174
+ # We need to figure out if the given stream is already binary.
175
+ # This can happen because the official docs recommend detaching
176
+ # the streams to get binary streams. Some code might do this, so
177
+ # we need to deal with this case explicitly.
178
+ if _is_binary_reader(stream, False):
179
+ return t.cast(t.BinaryIO, stream)
180
+
181
+ buf = getattr(stream, "buffer", None)
182
+
183
+ # Same situation here; this time we assume that the buffer is
184
+ # actually binary in case it's closed.
185
+ if buf is not None and _is_binary_reader(buf, True):
186
+ return t.cast(t.BinaryIO, buf)
187
+
188
+ return None
189
+
190
+
191
+ def _find_binary_writer(stream: t.IO[t.Any]) -> t.BinaryIO | None:
192
+ # We need to figure out if the given stream is already binary.
193
+ # This can happen because the official docs recommend detaching
194
+ # the streams to get binary streams. Some code might do this, so
195
+ # we need to deal with this case explicitly.
196
+ if _is_binary_writer(stream, False):
197
+ return t.cast(t.BinaryIO, stream)
198
+
199
+ buf = getattr(stream, "buffer", None)
200
+
201
+ # Same situation here; this time we assume that the buffer is
202
+ # actually binary in case it's closed.
203
+ if buf is not None and _is_binary_writer(buf, True):
204
+ return t.cast(t.BinaryIO, buf)
205
+
206
+ return None
207
+
208
+
209
+ def _stream_is_misconfigured(stream: t.TextIO) -> bool:
210
+ """A stream is misconfigured if its encoding is ASCII."""
211
+ # If the stream does not have an encoding set, we assume it's set
212
+ # to ASCII. This appears to happen in certain unittest
213
+ # environments. It's not quite clear what the correct behavior is
214
+ # but this at least will force Click to recover somehow.
215
+ return is_ascii_encoding(getattr(stream, "encoding", None) or "ascii")
216
+
217
+
218
+ def _is_compat_stream_attr(stream: t.TextIO, attr: str, value: str | None) -> bool:
219
+ """A stream attribute is compatible if it is equal to the
220
+ desired value or the desired value is unset and the attribute
221
+ has a value.
222
+ """
223
+ stream_value = getattr(stream, attr, None)
224
+ return stream_value == value or (value is None and stream_value is not None)
225
+
226
+
227
+ def _is_compatible_text_stream(
228
+ stream: t.TextIO, encoding: str | None, errors: str | None
229
+ ) -> bool:
230
+ """Check if a stream's encoding and errors attributes are
231
+ compatible with the desired values.
232
+ """
233
+ return _is_compat_stream_attr(
234
+ stream, "encoding", encoding
235
+ ) and _is_compat_stream_attr(stream, "errors", errors)
236
+
237
+
238
+ def _force_correct_text_stream(
239
+ text_stream: t.IO[t.Any],
240
+ encoding: str | None,
241
+ errors: str | None,
242
+ is_binary: t.Callable[[t.IO[t.Any], bool], bool],
243
+ find_binary: t.Callable[[t.IO[t.Any]], t.BinaryIO | None],
244
+ force_readable: bool = False,
245
+ force_writable: bool = False,
246
+ ) -> t.TextIO:
247
+ if is_binary(text_stream, False):
248
+ binary_reader = t.cast(t.BinaryIO, text_stream)
249
+ else:
250
+ text_stream = t.cast(t.TextIO, text_stream)
251
+ # If the stream looks compatible, and won't default to a
252
+ # misconfigured ascii encoding, return it as-is.
253
+ if _is_compatible_text_stream(text_stream, encoding, errors) and not (
254
+ encoding is None and _stream_is_misconfigured(text_stream)
255
+ ):
256
+ return text_stream
257
+
258
+ # Otherwise, get the underlying binary reader.
259
+ possible_binary_reader = find_binary(text_stream)
260
+
261
+ # If that's not possible, silently use the original reader
262
+ # and get mojibake instead of exceptions.
263
+ if possible_binary_reader is None:
264
+ return text_stream
265
+
266
+ binary_reader = possible_binary_reader
267
+
268
+ # Default errors to replace instead of strict in order to get
269
+ # something that works.
270
+ if errors is None:
271
+ errors = "replace"
272
+
273
+ # Wrap the binary stream in a text stream with the correct
274
+ # encoding parameters.
275
+ return _make_text_stream(
276
+ binary_reader,
277
+ encoding,
278
+ errors,
279
+ force_readable=force_readable,
280
+ force_writable=force_writable,
281
+ )
282
+
283
+
284
+ def _force_correct_text_reader(
285
+ text_reader: t.IO[t.Any],
286
+ encoding: str | None,
287
+ errors: str | None,
288
+ force_readable: bool = False,
289
+ ) -> t.TextIO:
290
+ return _force_correct_text_stream(
291
+ text_reader,
292
+ encoding,
293
+ errors,
294
+ _is_binary_reader,
295
+ _find_binary_reader,
296
+ force_readable=force_readable,
297
+ )
298
+
299
+
300
+ def _force_correct_text_writer(
301
+ text_writer: t.IO[t.Any],
302
+ encoding: str | None,
303
+ errors: str | None,
304
+ force_writable: bool = False,
305
+ ) -> t.TextIO:
306
+ return _force_correct_text_stream(
307
+ text_writer,
308
+ encoding,
309
+ errors,
310
+ _is_binary_writer,
311
+ _find_binary_writer,
312
+ force_writable=force_writable,
313
+ )
314
+
315
+
316
+ def get_binary_stdin() -> t.BinaryIO:
317
+ reader = _find_binary_reader(sys.stdin)
318
+ if reader is None:
319
+ raise RuntimeError("Was not able to determine binary stream for sys.stdin.")
320
+ return reader
321
+
322
+
323
+ def get_binary_stdout() -> t.BinaryIO:
324
+ writer = _find_binary_writer(sys.stdout)
325
+ if writer is None:
326
+ raise RuntimeError("Was not able to determine binary stream for sys.stdout.")
327
+ return writer
328
+
329
+
330
+ def get_binary_stderr() -> t.BinaryIO:
331
+ writer = _find_binary_writer(sys.stderr)
332
+ if writer is None:
333
+ raise RuntimeError("Was not able to determine binary stream for sys.stderr.")
334
+ return writer
335
+
336
+
337
+ def get_text_stdin(encoding: str | None = None, errors: str | None = None) -> t.TextIO:
338
+ rv = _get_windows_console_stream(sys.stdin, encoding, errors)
339
+ if rv is not None:
340
+ return rv
341
+ return _force_correct_text_reader(sys.stdin, encoding, errors, force_readable=True)
342
+
343
+
344
+ def get_text_stdout(encoding: str | None = None, errors: str | None = None) -> t.TextIO:
345
+ rv = _get_windows_console_stream(sys.stdout, encoding, errors)
346
+ if rv is not None:
347
+ return rv
348
+ return _force_correct_text_writer(sys.stdout, encoding, errors, force_writable=True)
349
+
350
+
351
+ def get_text_stderr(encoding: str | None = None, errors: str | None = None) -> t.TextIO:
352
+ rv = _get_windows_console_stream(sys.stderr, encoding, errors)
353
+ if rv is not None:
354
+ return rv
355
+ return _force_correct_text_writer(sys.stderr, encoding, errors, force_writable=True)
356
+
357
+
358
+ def _wrap_io_open(
359
+ file: str | os.PathLike[str] | int,
360
+ mode: str,
361
+ encoding: str | None,
362
+ errors: str | None,
363
+ ) -> t.IO[t.Any]:
364
+ """Handles not passing ``encoding`` and ``errors`` in binary mode."""
365
+ if "b" in mode:
366
+ return open(file, mode)
367
+
368
+ return open(file, mode, encoding=encoding, errors=errors)
369
+
370
+
371
+ def open_stream(
372
+ filename: str | os.PathLike[str],
373
+ mode: str = "r",
374
+ encoding: str | None = None,
375
+ errors: str | None = "strict",
376
+ atomic: bool = False,
377
+ ) -> tuple[t.IO[t.Any], bool]:
378
+ binary = "b" in mode
379
+ filename = os.fspath(filename)
380
+
381
+ # Standard streams first. These are simple because they ignore the
382
+ # atomic flag. Use fsdecode to handle Path("-").
383
+ if os.fsdecode(filename) == "-":
384
+ if any(m in mode for m in ["w", "a", "x"]):
385
+ if binary:
386
+ return get_binary_stdout(), False
387
+ return get_text_stdout(encoding=encoding, errors=errors), False
388
+ if binary:
389
+ return get_binary_stdin(), False
390
+ return get_text_stdin(encoding=encoding, errors=errors), False
391
+
392
+ # Non-atomic writes directly go out through the regular open functions.
393
+ if not atomic:
394
+ return _wrap_io_open(filename, mode, encoding, errors), True
395
+
396
+ # Some usability stuff for atomic writes
397
+ if "a" in mode:
398
+ raise ValueError(
399
+ "Appending to an existing file is not supported, because that"
400
+ " would involve an expensive `copy`-operation to a temporary"
401
+ " file. Open the file in normal `w`-mode and copy explicitly"
402
+ " if that's what you're after."
403
+ )
404
+ if "x" in mode:
405
+ raise ValueError("Use the `overwrite`-parameter instead.")
406
+ if "w" not in mode:
407
+ raise ValueError("Atomic writes only make sense with `w`-mode.")
408
+
409
+ # Atomic writes are more complicated. They work by opening a file
410
+ # as a proxy in the same folder and then using the fdopen
411
+ # functionality to wrap it in a Python file. Then we wrap it in an
412
+ # atomic file that moves the file over on close.
413
+ import errno
414
+ import random
415
+
416
+ try:
417
+ perm: int | None = os.stat(filename).st_mode
418
+ except OSError:
419
+ perm = None
420
+
421
+ flags = os.O_RDWR | os.O_CREAT | os.O_EXCL
422
+
423
+ if binary:
424
+ flags |= getattr(os, "O_BINARY", 0)
425
+
426
+ while True:
427
+ tmp_filename = os.path.join(
428
+ os.path.dirname(filename),
429
+ f".__atomic-write{random.randrange(1 << 32):08x}",
430
+ )
431
+ try:
432
+ fd = os.open(tmp_filename, flags, 0o666 if perm is None else perm)
433
+ break
434
+ except OSError as e:
435
+ if e.errno == errno.EEXIST or (
436
+ os.name == "nt"
437
+ and e.errno == errno.EACCES
438
+ and os.path.isdir(e.filename)
439
+ and os.access(e.filename, os.W_OK)
440
+ ):
441
+ continue
442
+ raise
443
+
444
+ if perm is not None:
445
+ os.chmod(tmp_filename, perm) # in case perm includes bits in umask
446
+
447
+ f = _wrap_io_open(fd, mode, encoding, errors)
448
+ af = _AtomicFile(f, tmp_filename, os.path.realpath(filename))
449
+ return t.cast(t.IO[t.Any], af), True
450
+
451
+
452
+ class _AtomicFile:
453
+ def __init__(self, f: t.IO[t.Any], tmp_filename: str, real_filename: str) -> None:
454
+ self._f = f
455
+ self._tmp_filename = tmp_filename
456
+ self._real_filename = real_filename
457
+ self.closed = False
458
+
459
+ @property
460
+ def name(self) -> str:
461
+ return self._real_filename
462
+
463
+ def close(self, delete: bool = False) -> None:
464
+ if self.closed:
465
+ return
466
+ self._f.close()
467
+ os.replace(self._tmp_filename, self._real_filename)
468
+ self.closed = True
469
+
470
+ def __getattr__(self, name: str) -> t.Any:
471
+ return getattr(self._f, name)
472
+
473
+ def __enter__(self) -> _AtomicFile:
474
+ return self
475
+
476
+ def __exit__(
477
+ self,
478
+ exc_type: type[BaseException] | None,
479
+ exc_value: BaseException | None,
480
+ tb: TracebackType | None,
481
+ ) -> None:
482
+ self.close(delete=exc_type is not None)
483
+
484
+ def __repr__(self) -> str:
485
+ return repr(self._f)
486
+
487
+
488
+ def strip_ansi(value: str) -> str:
489
+ return _ansi_re.sub("", value)
490
+
491
+
492
+ def _is_jupyter_kernel_output(stream: t.IO[t.Any]) -> bool:
493
+ while isinstance(stream, (_FixupStream, _NonClosingTextIOWrapper)):
494
+ stream = stream._stream
495
+
496
+ return stream.__class__.__module__.startswith("ipykernel.")
497
+
498
+
499
+ def should_strip_ansi(
500
+ stream: t.IO[t.Any] | None = None, color: bool | None = None
501
+ ) -> bool:
502
+ if color is None:
503
+ if stream is None:
504
+ stream = sys.stdin
505
+ return not isatty(stream) and not _is_jupyter_kernel_output(stream)
506
+ return not color
507
+
508
+
509
+ # On Windows, wrap the output streams with colorama to support ANSI
510
+ # color codes.
511
+ # NOTE: double check is needed so mypy does not analyze this on Linux
512
+ if sys.platform.startswith("win") and WIN:
513
+ from ._winconsole import _get_windows_console_stream
514
+
515
+ def _get_argv_encoding() -> str:
516
+ import locale
517
+
518
+ return locale.getpreferredencoding()
519
+
520
+ _ansi_stream_wrappers: cabc.MutableMapping[t.TextIO, t.TextIO] = WeakKeyDictionary()
521
+
522
+ def auto_wrap_for_ansi(stream: t.TextIO, color: bool | None = None) -> t.TextIO:
523
+ """Support ANSI color and style codes on Windows by wrapping a
524
+ stream with colorama.
525
+ """
526
+ try:
527
+ cached = _ansi_stream_wrappers.get(stream)
528
+ except Exception:
529
+ cached = None
530
+
531
+ if cached is not None:
532
+ return cached
533
+
534
+ import colorama
535
+
536
+ strip = should_strip_ansi(stream, color)
537
+ ansi_wrapper = colorama.AnsiToWin32(stream, strip=strip)
538
+ rv = t.cast(t.TextIO, ansi_wrapper.stream)
539
+ _write = rv.write
540
+
541
+ def _safe_write(s: str) -> int:
542
+ try:
543
+ return _write(s)
544
+ except BaseException:
545
+ ansi_wrapper.reset_all()
546
+ raise
547
+
548
+ rv.write = _safe_write # type: ignore[method-assign]
549
+
550
+ try:
551
+ _ansi_stream_wrappers[stream] = rv
552
+ except Exception:
553
+ pass
554
+
555
+ return rv
556
+
557
+ else:
558
+
559
+ def _get_argv_encoding() -> str:
560
+ return getattr(sys.stdin, "encoding", None) or sys.getfilesystemencoding()
561
+
562
+ def _get_windows_console_stream(
563
+ f: t.TextIO, encoding: str | None, errors: str | None
564
+ ) -> t.TextIO | None:
565
+ return None
566
+
567
+
568
+ def term_len(x: str) -> int:
569
+ return len(strip_ansi(x))
570
+
571
+
572
+ def isatty(stream: t.IO[t.Any]) -> bool:
573
+ try:
574
+ return stream.isatty()
575
+ except Exception:
576
+ return False
577
+
578
+
579
+ def _make_cached_stream_func(
580
+ src_func: t.Callable[[], t.TextIO | None],
581
+ wrapper_func: t.Callable[[], t.TextIO],
582
+ ) -> t.Callable[[], t.TextIO | None]:
583
+ cache: cabc.MutableMapping[t.TextIO, t.TextIO] = WeakKeyDictionary()
584
+
585
+ def func() -> t.TextIO | None:
586
+ stream = src_func()
587
+
588
+ if stream is None:
589
+ return None
590
+
591
+ try:
592
+ rv = cache.get(stream)
593
+ except Exception:
594
+ rv = None
595
+ if rv is not None:
596
+ return rv
597
+ rv = wrapper_func()
598
+ try:
599
+ cache[stream] = rv
600
+ except Exception:
601
+ pass
602
+ return rv
603
+
604
+ return func
605
+
606
+
607
+ _default_text_stdin = _make_cached_stream_func(lambda: sys.stdin, get_text_stdin)
608
+ _default_text_stdout = _make_cached_stream_func(lambda: sys.stdout, get_text_stdout)
609
+ _default_text_stderr = _make_cached_stream_func(lambda: sys.stderr, get_text_stderr)
610
+
611
+
612
+ binary_streams: cabc.Mapping[str, t.Callable[[], t.BinaryIO]] = {
613
+ "stdin": get_binary_stdin,
614
+ "stdout": get_binary_stdout,
615
+ "stderr": get_binary_stderr,
616
+ }
617
+
618
+ text_streams: cabc.Mapping[str, t.Callable[[str | None, str | None], t.TextIO]] = {
619
+ "stdin": get_text_stdin,
620
+ "stdout": get_text_stdout,
621
+ "stderr": get_text_stderr,
622
+ }
lib/python3.12/site-packages/click/_termui_impl.py ADDED
@@ -0,0 +1,839 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ This module contains implementations for the termui module. To keep the
3
+ import time of Click down, some infrequently used functionality is
4
+ placed in this module and only imported as needed.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import collections.abc as cabc
10
+ import contextlib
11
+ import math
12
+ import os
13
+ import shlex
14
+ import sys
15
+ import time
16
+ import typing as t
17
+ from gettext import gettext as _
18
+ from io import StringIO
19
+ from pathlib import Path
20
+ from shutil import which
21
+ from types import TracebackType
22
+
23
+ from ._compat import _default_text_stdout
24
+ from ._compat import CYGWIN
25
+ from ._compat import get_best_encoding
26
+ from ._compat import isatty
27
+ from ._compat import open_stream
28
+ from ._compat import strip_ansi
29
+ from ._compat import term_len
30
+ from ._compat import WIN
31
+ from .exceptions import ClickException
32
+ from .utils import echo
33
+
34
+ V = t.TypeVar("V")
35
+
36
+ if os.name == "nt":
37
+ BEFORE_BAR = "\r"
38
+ AFTER_BAR = "\n"
39
+ else:
40
+ BEFORE_BAR = "\r\033[?25l"
41
+ AFTER_BAR = "\033[?25h\n"
42
+
43
+
44
+ class ProgressBar(t.Generic[V]):
45
+ def __init__(
46
+ self,
47
+ iterable: cabc.Iterable[V] | None,
48
+ length: int | None = None,
49
+ fill_char: str = "#",
50
+ empty_char: str = " ",
51
+ bar_template: str = "%(bar)s",
52
+ info_sep: str = " ",
53
+ hidden: bool = False,
54
+ show_eta: bool = True,
55
+ show_percent: bool | None = None,
56
+ show_pos: bool = False,
57
+ item_show_func: t.Callable[[V | None], str | None] | None = None,
58
+ label: str | None = None,
59
+ file: t.TextIO | None = None,
60
+ color: bool | None = None,
61
+ update_min_steps: int = 1,
62
+ width: int = 30,
63
+ ) -> None:
64
+ self.fill_char = fill_char
65
+ self.empty_char = empty_char
66
+ self.bar_template = bar_template
67
+ self.info_sep = info_sep
68
+ self.hidden = hidden
69
+ self.show_eta = show_eta
70
+ self.show_percent = show_percent
71
+ self.show_pos = show_pos
72
+ self.item_show_func = item_show_func
73
+ self.label: str = label or ""
74
+
75
+ if file is None:
76
+ file = _default_text_stdout()
77
+
78
+ # There are no standard streams attached to write to. For example,
79
+ # pythonw on Windows.
80
+ if file is None:
81
+ file = StringIO()
82
+
83
+ self.file = file
84
+ self.color = color
85
+ self.update_min_steps = update_min_steps
86
+ self._completed_intervals = 0
87
+ self.width: int = width
88
+ self.autowidth: bool = width == 0
89
+
90
+ if length is None:
91
+ from operator import length_hint
92
+
93
+ length = length_hint(iterable, -1)
94
+
95
+ if length == -1:
96
+ length = None
97
+ if iterable is None:
98
+ if length is None:
99
+ raise TypeError("iterable or length is required")
100
+ iterable = t.cast("cabc.Iterable[V]", range(length))
101
+ self.iter: cabc.Iterable[V] = iter(iterable)
102
+ self.length = length
103
+ self.pos: int = 0
104
+ self.avg: list[float] = []
105
+ self.last_eta: float
106
+ self.start: float
107
+ self.start = self.last_eta = time.time()
108
+ self.eta_known: bool = False
109
+ self.finished: bool = False
110
+ self.max_width: int | None = None
111
+ self.entered: bool = False
112
+ self.current_item: V | None = None
113
+ self._is_atty = isatty(self.file)
114
+ self._last_line: str | None = None
115
+
116
+ def __enter__(self) -> ProgressBar[V]:
117
+ self.entered = True
118
+ self.render_progress()
119
+ return self
120
+
121
+ def __exit__(
122
+ self,
123
+ exc_type: type[BaseException] | None,
124
+ exc_value: BaseException | None,
125
+ tb: TracebackType | None,
126
+ ) -> None:
127
+ self.render_finish()
128
+
129
+ def __iter__(self) -> cabc.Iterator[V]:
130
+ if not self.entered:
131
+ raise RuntimeError("You need to use progress bars in a with block.")
132
+ self.render_progress()
133
+ return self.generator()
134
+
135
+ def __next__(self) -> V:
136
+ # Iteration is defined in terms of a generator function,
137
+ # returned by iter(self); use that to define next(). This works
138
+ # because `self.iter` is an iterable consumed by that generator,
139
+ # so it is re-entry safe. Calling `next(self.generator())`
140
+ # twice works and does "what you want".
141
+ return next(iter(self))
142
+
143
+ def render_finish(self) -> None:
144
+ if self.hidden or not self._is_atty:
145
+ return
146
+ self.file.write(AFTER_BAR)
147
+ self.file.flush()
148
+
149
+ @property
150
+ def pct(self) -> float:
151
+ if self.finished:
152
+ return 1.0
153
+ return min(self.pos / (float(self.length or 1) or 1), 1.0)
154
+
155
+ @property
156
+ def time_per_iteration(self) -> float:
157
+ if not self.avg:
158
+ return 0.0
159
+ return sum(self.avg) / float(len(self.avg))
160
+
161
+ @property
162
+ def eta(self) -> float:
163
+ if self.length is not None and not self.finished:
164
+ return self.time_per_iteration * (self.length - self.pos)
165
+ return 0.0
166
+
167
+ def format_eta(self) -> str:
168
+ if self.eta_known:
169
+ t = int(self.eta)
170
+ seconds = t % 60
171
+ t //= 60
172
+ minutes = t % 60
173
+ t //= 60
174
+ hours = t % 24
175
+ t //= 24
176
+ if t > 0:
177
+ return f"{t}d {hours:02}:{minutes:02}:{seconds:02}"
178
+ else:
179
+ return f"{hours:02}:{minutes:02}:{seconds:02}"
180
+ return ""
181
+
182
+ def format_pos(self) -> str:
183
+ pos = str(self.pos)
184
+ if self.length is not None:
185
+ pos += f"/{self.length}"
186
+ return pos
187
+
188
+ def format_pct(self) -> str:
189
+ return f"{int(self.pct * 100): 4}%"[1:]
190
+
191
+ def format_bar(self) -> str:
192
+ if self.length is not None:
193
+ bar_length = int(self.pct * self.width)
194
+ bar = self.fill_char * bar_length
195
+ bar += self.empty_char * (self.width - bar_length)
196
+ elif self.finished:
197
+ bar = self.fill_char * self.width
198
+ else:
199
+ chars = list(self.empty_char * (self.width or 1))
200
+ if self.time_per_iteration != 0:
201
+ chars[
202
+ int(
203
+ (math.cos(self.pos * self.time_per_iteration) / 2.0 + 0.5)
204
+ * self.width
205
+ )
206
+ ] = self.fill_char
207
+ bar = "".join(chars)
208
+ return bar
209
+
210
+ def format_progress_line(self) -> str:
211
+ show_percent = self.show_percent
212
+
213
+ info_bits = []
214
+ if self.length is not None and show_percent is None:
215
+ show_percent = not self.show_pos
216
+
217
+ if self.show_pos:
218
+ info_bits.append(self.format_pos())
219
+ if show_percent:
220
+ info_bits.append(self.format_pct())
221
+ if self.show_eta and self.eta_known and not self.finished:
222
+ info_bits.append(self.format_eta())
223
+ if self.item_show_func is not None:
224
+ item_info = self.item_show_func(self.current_item)
225
+ if item_info is not None:
226
+ info_bits.append(item_info)
227
+
228
+ return (
229
+ self.bar_template
230
+ % {
231
+ "label": self.label,
232
+ "bar": self.format_bar(),
233
+ "info": self.info_sep.join(info_bits),
234
+ }
235
+ ).rstrip()
236
+
237
+ def render_progress(self) -> None:
238
+ import shutil
239
+
240
+ if self.hidden:
241
+ return
242
+
243
+ if not self._is_atty:
244
+ # Only output the label once if the output is not a TTY.
245
+ if self._last_line != self.label:
246
+ self._last_line = self.label
247
+ echo(self.label, file=self.file, color=self.color)
248
+ return
249
+
250
+ buf = []
251
+ # Update width in case the terminal has been resized
252
+ if self.autowidth:
253
+ old_width = self.width
254
+ self.width = 0
255
+ clutter_length = term_len(self.format_progress_line())
256
+ new_width = max(0, shutil.get_terminal_size().columns - clutter_length)
257
+ if new_width < old_width and self.max_width is not None:
258
+ buf.append(BEFORE_BAR)
259
+ buf.append(" " * self.max_width)
260
+ self.max_width = new_width
261
+ self.width = new_width
262
+
263
+ clear_width = self.width
264
+ if self.max_width is not None:
265
+ clear_width = self.max_width
266
+
267
+ buf.append(BEFORE_BAR)
268
+ line = self.format_progress_line()
269
+ line_len = term_len(line)
270
+ if self.max_width is None or self.max_width < line_len:
271
+ self.max_width = line_len
272
+
273
+ buf.append(line)
274
+ buf.append(" " * (clear_width - line_len))
275
+ line = "".join(buf)
276
+ # Render the line only if it changed.
277
+
278
+ if line != self._last_line:
279
+ self._last_line = line
280
+ echo(line, file=self.file, color=self.color, nl=False)
281
+ self.file.flush()
282
+
283
+ def make_step(self, n_steps: int) -> None:
284
+ self.pos += n_steps
285
+ if self.length is not None and self.pos >= self.length:
286
+ self.finished = True
287
+
288
+ if (time.time() - self.last_eta) < 1.0:
289
+ return
290
+
291
+ self.last_eta = time.time()
292
+
293
+ # self.avg is a rolling list of length <= 7 of steps where steps are
294
+ # defined as time elapsed divided by the total progress through
295
+ # self.length.
296
+ if self.pos:
297
+ step = (time.time() - self.start) / self.pos
298
+ else:
299
+ step = time.time() - self.start
300
+
301
+ self.avg = self.avg[-6:] + [step]
302
+
303
+ self.eta_known = self.length is not None
304
+
305
+ def update(self, n_steps: int, current_item: V | None = None) -> None:
306
+ """Update the progress bar by advancing a specified number of
307
+ steps, and optionally set the ``current_item`` for this new
308
+ position.
309
+
310
+ :param n_steps: Number of steps to advance.
311
+ :param current_item: Optional item to set as ``current_item``
312
+ for the updated position.
313
+
314
+ .. versionchanged:: 8.0
315
+ Added the ``current_item`` optional parameter.
316
+
317
+ .. versionchanged:: 8.0
318
+ Only render when the number of steps meets the
319
+ ``update_min_steps`` threshold.
320
+ """
321
+ if current_item is not None:
322
+ self.current_item = current_item
323
+
324
+ self._completed_intervals += n_steps
325
+
326
+ if self._completed_intervals >= self.update_min_steps:
327
+ self.make_step(self._completed_intervals)
328
+ self.render_progress()
329
+ self._completed_intervals = 0
330
+
331
+ def finish(self) -> None:
332
+ self.eta_known = False
333
+ self.current_item = None
334
+ self.finished = True
335
+
336
+ def generator(self) -> cabc.Iterator[V]:
337
+ """Return a generator which yields the items added to the bar
338
+ during construction, and updates the progress bar *after* the
339
+ yielded block returns.
340
+ """
341
+ # WARNING: the iterator interface for `ProgressBar` relies on
342
+ # this and only works because this is a simple generator which
343
+ # doesn't create or manage additional state. If this function
344
+ # changes, the impact should be evaluated both against
345
+ # `iter(bar)` and `next(bar)`. `next()` in particular may call
346
+ # `self.generator()` repeatedly, and this must remain safe in
347
+ # order for that interface to work.
348
+ if not self.entered:
349
+ raise RuntimeError("You need to use progress bars in a with block.")
350
+
351
+ if not self._is_atty:
352
+ yield from self.iter
353
+ else:
354
+ for rv in self.iter:
355
+ self.current_item = rv
356
+
357
+ # This allows show_item_func to be updated before the
358
+ # item is processed. Only trigger at the beginning of
359
+ # the update interval.
360
+ if self._completed_intervals == 0:
361
+ self.render_progress()
362
+
363
+ yield rv
364
+ self.update(1)
365
+
366
+ self.finish()
367
+ self.render_progress()
368
+
369
+
370
+ def pager(generator: cabc.Iterable[str], color: bool | None = None) -> None:
371
+ """Decide what method to use for paging through text."""
372
+ stdout = _default_text_stdout()
373
+
374
+ # There are no standard streams attached to write to. For example,
375
+ # pythonw on Windows.
376
+ if stdout is None:
377
+ stdout = StringIO()
378
+
379
+ if not isatty(sys.stdin) or not isatty(stdout):
380
+ return _nullpager(stdout, generator, color)
381
+
382
+ # Split and normalize the pager command into parts.
383
+ pager_cmd_parts = shlex.split(os.environ.get("PAGER", ""), posix=False)
384
+ if pager_cmd_parts:
385
+ if WIN:
386
+ if _tempfilepager(generator, pager_cmd_parts, color):
387
+ return
388
+ elif _pipepager(generator, pager_cmd_parts, color):
389
+ return
390
+
391
+ if os.environ.get("TERM") in ("dumb", "emacs"):
392
+ return _nullpager(stdout, generator, color)
393
+ if (WIN or sys.platform.startswith("os2")) and _tempfilepager(
394
+ generator, ["more"], color
395
+ ):
396
+ return
397
+ if _pipepager(generator, ["less"], color):
398
+ return
399
+
400
+ import tempfile
401
+
402
+ fd, filename = tempfile.mkstemp()
403
+ os.close(fd)
404
+ try:
405
+ if _pipepager(generator, ["more"], color):
406
+ return
407
+ return _nullpager(stdout, generator, color)
408
+ finally:
409
+ os.unlink(filename)
410
+
411
+
412
+ def _pipepager(
413
+ generator: cabc.Iterable[str], cmd_parts: list[str], color: bool | None
414
+ ) -> bool:
415
+ """Page through text by feeding it to another program. Invoking a
416
+ pager through this might support colors.
417
+
418
+ Returns `True` if the command was found, `False` otherwise and thus another
419
+ pager should be attempted.
420
+ """
421
+ # Split the command into the invoked CLI and its parameters.
422
+ if not cmd_parts:
423
+ return False
424
+ cmd = cmd_parts[0]
425
+ cmd_params = cmd_parts[1:]
426
+
427
+ cmd_filepath = which(cmd)
428
+ if not cmd_filepath:
429
+ return False
430
+ # Resolves symlinks and produces a normalized absolute path string.
431
+ cmd_path = Path(cmd_filepath).resolve()
432
+ cmd_name = cmd_path.name
433
+
434
+ import subprocess
435
+
436
+ # Make a local copy of the environment to not affect the global one.
437
+ env = dict(os.environ)
438
+
439
+ # If we're piping to less and the user hasn't decided on colors, we enable
440
+ # them by default we find the -R flag in the command line arguments.
441
+ if color is None and cmd_name == "less":
442
+ less_flags = f"{os.environ.get('LESS', '')}{' '.join(cmd_params)}"
443
+ if not less_flags:
444
+ env["LESS"] = "-R"
445
+ color = True
446
+ elif "r" in less_flags or "R" in less_flags:
447
+ color = True
448
+
449
+ c = subprocess.Popen(
450
+ [str(cmd_path)] + cmd_params,
451
+ shell=True,
452
+ stdin=subprocess.PIPE,
453
+ env=env,
454
+ errors="replace",
455
+ text=True,
456
+ )
457
+ assert c.stdin is not None
458
+ try:
459
+ for text in generator:
460
+ if not color:
461
+ text = strip_ansi(text)
462
+
463
+ c.stdin.write(text)
464
+ except BrokenPipeError:
465
+ # In case the pager exited unexpectedly, ignore the broken pipe error.
466
+ pass
467
+ except Exception as e:
468
+ # In case there is an exception we want to close the pager immediately
469
+ # and let the caller handle it.
470
+ # Otherwise the pager will keep running, and the user may not notice
471
+ # the error message, or worse yet it may leave the terminal in a broken state.
472
+ c.terminate()
473
+ raise e
474
+ finally:
475
+ # We must close stdin and wait for the pager to exit before we continue
476
+ try:
477
+ c.stdin.close()
478
+ # Close implies flush, so it might throw a BrokenPipeError if the pager
479
+ # process exited already.
480
+ except BrokenPipeError:
481
+ pass
482
+
483
+ # Less doesn't respect ^C, but catches it for its own UI purposes (aborting
484
+ # search or other commands inside less).
485
+ #
486
+ # That means when the user hits ^C, the parent process (click) terminates,
487
+ # but less is still alive, paging the output and messing up the terminal.
488
+ #
489
+ # If the user wants to make the pager exit on ^C, they should set
490
+ # `LESS='-K'`. It's not our decision to make.
491
+ while True:
492
+ try:
493
+ c.wait()
494
+ except KeyboardInterrupt:
495
+ pass
496
+ else:
497
+ break
498
+
499
+ return True
500
+
501
+
502
+ def _tempfilepager(
503
+ generator: cabc.Iterable[str], cmd_parts: list[str], color: bool | None
504
+ ) -> bool:
505
+ """Page through text by invoking a program on a temporary file.
506
+
507
+ Returns `True` if the command was found, `False` otherwise and thus another
508
+ pager should be attempted.
509
+ """
510
+ # Split the command into the invoked CLI and its parameters.
511
+ if not cmd_parts:
512
+ return False
513
+ cmd = cmd_parts[0]
514
+
515
+ cmd_filepath = which(cmd)
516
+ if not cmd_filepath:
517
+ return False
518
+ # Resolves symlinks and produces a normalized absolute path string.
519
+ cmd_path = Path(cmd_filepath).resolve()
520
+
521
+ import subprocess
522
+ import tempfile
523
+
524
+ fd, filename = tempfile.mkstemp()
525
+ # TODO: This never terminates if the passed generator never terminates.
526
+ text = "".join(generator)
527
+ if not color:
528
+ text = strip_ansi(text)
529
+ encoding = get_best_encoding(sys.stdout)
530
+ with open_stream(filename, "wb")[0] as f:
531
+ f.write(text.encode(encoding))
532
+ try:
533
+ subprocess.call([str(cmd_path), filename])
534
+ except OSError:
535
+ # Command not found
536
+ pass
537
+ finally:
538
+ os.close(fd)
539
+ os.unlink(filename)
540
+
541
+ return True
542
+
543
+
544
+ def _nullpager(
545
+ stream: t.TextIO, generator: cabc.Iterable[str], color: bool | None
546
+ ) -> None:
547
+ """Simply print unformatted text. This is the ultimate fallback."""
548
+ for text in generator:
549
+ if not color:
550
+ text = strip_ansi(text)
551
+ stream.write(text)
552
+
553
+
554
+ class Editor:
555
+ def __init__(
556
+ self,
557
+ editor: str | None = None,
558
+ env: cabc.Mapping[str, str] | None = None,
559
+ require_save: bool = True,
560
+ extension: str = ".txt",
561
+ ) -> None:
562
+ self.editor = editor
563
+ self.env = env
564
+ self.require_save = require_save
565
+ self.extension = extension
566
+
567
+ def get_editor(self) -> str:
568
+ if self.editor is not None:
569
+ return self.editor
570
+ for key in "VISUAL", "EDITOR":
571
+ rv = os.environ.get(key)
572
+ if rv:
573
+ return rv
574
+ if WIN:
575
+ return "notepad"
576
+ for editor in "sensible-editor", "vim", "nano":
577
+ if which(editor) is not None:
578
+ return editor
579
+ return "vi"
580
+
581
+ def edit_files(self, filenames: cabc.Iterable[str]) -> None:
582
+ import subprocess
583
+
584
+ editor = self.get_editor()
585
+ environ: dict[str, str] | None = None
586
+
587
+ if self.env:
588
+ environ = os.environ.copy()
589
+ environ.update(self.env)
590
+
591
+ exc_filename = " ".join(f'"{filename}"' for filename in filenames)
592
+
593
+ try:
594
+ c = subprocess.Popen(
595
+ args=f"{editor} {exc_filename}", env=environ, shell=True
596
+ )
597
+ exit_code = c.wait()
598
+ if exit_code != 0:
599
+ raise ClickException(
600
+ _("{editor}: Editing failed").format(editor=editor)
601
+ )
602
+ except OSError as e:
603
+ raise ClickException(
604
+ _("{editor}: Editing failed: {e}").format(editor=editor, e=e)
605
+ ) from e
606
+
607
+ @t.overload
608
+ def edit(self, text: bytes | bytearray) -> bytes | None: ...
609
+
610
+ # We cannot know whether or not the type expected is str or bytes when None
611
+ # is passed, so str is returned as that was what was done before.
612
+ @t.overload
613
+ def edit(self, text: str | None) -> str | None: ...
614
+
615
+ def edit(self, text: str | bytes | bytearray | None) -> str | bytes | None:
616
+ import tempfile
617
+
618
+ if text is None:
619
+ data = b""
620
+ elif isinstance(text, (bytes, bytearray)):
621
+ data = text
622
+ else:
623
+ if text and not text.endswith("\n"):
624
+ text += "\n"
625
+
626
+ if WIN:
627
+ data = text.replace("\n", "\r\n").encode("utf-8-sig")
628
+ else:
629
+ data = text.encode("utf-8")
630
+
631
+ fd, name = tempfile.mkstemp(prefix="editor-", suffix=self.extension)
632
+ f: t.BinaryIO
633
+
634
+ try:
635
+ with os.fdopen(fd, "wb") as f:
636
+ f.write(data)
637
+
638
+ # If the filesystem resolution is 1 second, like Mac OS
639
+ # 10.12 Extended, or 2 seconds, like FAT32, and the editor
640
+ # closes very fast, require_save can fail. Set the modified
641
+ # time to be 2 seconds in the past to work around this.
642
+ os.utime(name, (os.path.getatime(name), os.path.getmtime(name) - 2))
643
+ # Depending on the resolution, the exact value might not be
644
+ # recorded, so get the new recorded value.
645
+ timestamp = os.path.getmtime(name)
646
+
647
+ self.edit_files((name,))
648
+
649
+ if self.require_save and os.path.getmtime(name) == timestamp:
650
+ return None
651
+
652
+ with open(name, "rb") as f:
653
+ rv = f.read()
654
+
655
+ if isinstance(text, (bytes, bytearray)):
656
+ return rv
657
+
658
+ return rv.decode("utf-8-sig").replace("\r\n", "\n")
659
+ finally:
660
+ os.unlink(name)
661
+
662
+
663
+ def open_url(url: str, wait: bool = False, locate: bool = False) -> int:
664
+ import subprocess
665
+
666
+ def _unquote_file(url: str) -> str:
667
+ from urllib.parse import unquote
668
+
669
+ if url.startswith("file://"):
670
+ url = unquote(url[7:])
671
+
672
+ return url
673
+
674
+ if sys.platform == "darwin":
675
+ args = ["open"]
676
+ if wait:
677
+ args.append("-W")
678
+ if locate:
679
+ args.append("-R")
680
+ args.append(_unquote_file(url))
681
+ null = open("/dev/null", "w")
682
+ try:
683
+ return subprocess.Popen(args, stderr=null).wait()
684
+ finally:
685
+ null.close()
686
+ elif WIN:
687
+ if locate:
688
+ url = _unquote_file(url)
689
+ args = ["explorer", f"/select,{url}"]
690
+ else:
691
+ args = ["start"]
692
+ if wait:
693
+ args.append("/WAIT")
694
+ args.append("")
695
+ args.append(url)
696
+ try:
697
+ return subprocess.call(args)
698
+ except OSError:
699
+ # Command not found
700
+ return 127
701
+ elif CYGWIN:
702
+ if locate:
703
+ url = _unquote_file(url)
704
+ args = ["cygstart", os.path.dirname(url)]
705
+ else:
706
+ args = ["cygstart"]
707
+ if wait:
708
+ args.append("-w")
709
+ args.append(url)
710
+ try:
711
+ return subprocess.call(args)
712
+ except OSError:
713
+ # Command not found
714
+ return 127
715
+
716
+ try:
717
+ if locate:
718
+ url = os.path.dirname(_unquote_file(url)) or "."
719
+ else:
720
+ url = _unquote_file(url)
721
+ c = subprocess.Popen(["xdg-open", url])
722
+ if wait:
723
+ return c.wait()
724
+ return 0
725
+ except OSError:
726
+ if url.startswith(("http://", "https://")) and not locate and not wait:
727
+ import webbrowser
728
+
729
+ webbrowser.open(url)
730
+ return 0
731
+ return 1
732
+
733
+
734
+ def _translate_ch_to_exc(ch: str) -> None:
735
+ if ch == "\x03":
736
+ raise KeyboardInterrupt()
737
+
738
+ if ch == "\x04" and not WIN: # Unix-like, Ctrl+D
739
+ raise EOFError()
740
+
741
+ if ch == "\x1a" and WIN: # Windows, Ctrl+Z
742
+ raise EOFError()
743
+
744
+ return None
745
+
746
+
747
+ if sys.platform == "win32":
748
+ import msvcrt
749
+
750
+ @contextlib.contextmanager
751
+ def raw_terminal() -> cabc.Iterator[int]:
752
+ yield -1
753
+
754
+ def getchar(echo: bool) -> str:
755
+ # The function `getch` will return a bytes object corresponding to
756
+ # the pressed character. Since Windows 10 build 1803, it will also
757
+ # return \x00 when called a second time after pressing a regular key.
758
+ #
759
+ # `getwch` does not share this probably-bugged behavior. Moreover, it
760
+ # returns a Unicode object by default, which is what we want.
761
+ #
762
+ # Either of these functions will return \x00 or \xe0 to indicate
763
+ # a special key, and you need to call the same function again to get
764
+ # the "rest" of the code. The fun part is that \u00e0 is
765
+ # "latin small letter a with grave", so if you type that on a French
766
+ # keyboard, you _also_ get a \xe0.
767
+ # E.g., consider the Up arrow. This returns \xe0 and then \x48. The
768
+ # resulting Unicode string reads as "a with grave" + "capital H".
769
+ # This is indistinguishable from when the user actually types
770
+ # "a with grave" and then "capital H".
771
+ #
772
+ # When \xe0 is returned, we assume it's part of a special-key sequence
773
+ # and call `getwch` again, but that means that when the user types
774
+ # the \u00e0 character, `getchar` doesn't return until a second
775
+ # character is typed.
776
+ # The alternative is returning immediately, but that would mess up
777
+ # cross-platform handling of arrow keys and others that start with
778
+ # \xe0. Another option is using `getch`, but then we can't reliably
779
+ # read non-ASCII characters, because return values of `getch` are
780
+ # limited to the current 8-bit codepage.
781
+ #
782
+ # Anyway, Click doesn't claim to do this Right(tm), and using `getwch`
783
+ # is doing the right thing in more situations than with `getch`.
784
+
785
+ if echo:
786
+ func = t.cast(t.Callable[[], str], msvcrt.getwche)
787
+ else:
788
+ func = t.cast(t.Callable[[], str], msvcrt.getwch)
789
+
790
+ rv = func()
791
+
792
+ if rv in ("\x00", "\xe0"):
793
+ # \x00 and \xe0 are control characters that indicate special key,
794
+ # see above.
795
+ rv += func()
796
+
797
+ _translate_ch_to_exc(rv)
798
+ return rv
799
+
800
+ else:
801
+ import termios
802
+ import tty
803
+
804
+ @contextlib.contextmanager
805
+ def raw_terminal() -> cabc.Iterator[int]:
806
+ f: t.TextIO | None
807
+ fd: int
808
+
809
+ if not isatty(sys.stdin):
810
+ f = open("/dev/tty")
811
+ fd = f.fileno()
812
+ else:
813
+ fd = sys.stdin.fileno()
814
+ f = None
815
+
816
+ try:
817
+ old_settings = termios.tcgetattr(fd)
818
+
819
+ try:
820
+ tty.setraw(fd)
821
+ yield fd
822
+ finally:
823
+ termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
824
+ sys.stdout.flush()
825
+
826
+ if f is not None:
827
+ f.close()
828
+ except termios.error:
829
+ pass
830
+
831
+ def getchar(echo: bool) -> str:
832
+ with raw_terminal() as fd:
833
+ ch = os.read(fd, 32).decode(get_best_encoding(sys.stdin), "replace")
834
+
835
+ if echo and isatty(sys.stdout):
836
+ sys.stdout.write(ch)
837
+
838
+ _translate_ch_to_exc(ch)
839
+ return ch
lib/python3.12/site-packages/click/_textwrap.py ADDED
@@ -0,0 +1,51 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import collections.abc as cabc
4
+ import textwrap
5
+ from contextlib import contextmanager
6
+
7
+
8
+ class TextWrapper(textwrap.TextWrapper):
9
+ def _handle_long_word(
10
+ self,
11
+ reversed_chunks: list[str],
12
+ cur_line: list[str],
13
+ cur_len: int,
14
+ width: int,
15
+ ) -> None:
16
+ space_left = max(width - cur_len, 1)
17
+
18
+ if self.break_long_words:
19
+ last = reversed_chunks[-1]
20
+ cut = last[:space_left]
21
+ res = last[space_left:]
22
+ cur_line.append(cut)
23
+ reversed_chunks[-1] = res
24
+ elif not cur_line:
25
+ cur_line.append(reversed_chunks.pop())
26
+
27
+ @contextmanager
28
+ def extra_indent(self, indent: str) -> cabc.Iterator[None]:
29
+ old_initial_indent = self.initial_indent
30
+ old_subsequent_indent = self.subsequent_indent
31
+ self.initial_indent += indent
32
+ self.subsequent_indent += indent
33
+
34
+ try:
35
+ yield
36
+ finally:
37
+ self.initial_indent = old_initial_indent
38
+ self.subsequent_indent = old_subsequent_indent
39
+
40
+ def indent_only(self, text: str) -> str:
41
+ rv = []
42
+
43
+ for idx, line in enumerate(text.splitlines()):
44
+ indent = self.initial_indent
45
+
46
+ if idx > 0:
47
+ indent = self.subsequent_indent
48
+
49
+ rv.append(f"{indent}{line}")
50
+
51
+ return "\n".join(rv)
lib/python3.12/site-packages/click/_winconsole.py ADDED
@@ -0,0 +1,296 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # This module is based on the excellent work by Adam Bartoš who
2
+ # provided a lot of what went into the implementation here in
3
+ # the discussion to issue1602 in the Python bug tracker.
4
+ #
5
+ # There are some general differences in regards to how this works
6
+ # compared to the original patches as we do not need to patch
7
+ # the entire interpreter but just work in our little world of
8
+ # echo and prompt.
9
+ from __future__ import annotations
10
+
11
+ import collections.abc as cabc
12
+ import io
13
+ import sys
14
+ import time
15
+ import typing as t
16
+ from ctypes import Array
17
+ from ctypes import byref
18
+ from ctypes import c_char
19
+ from ctypes import c_char_p
20
+ from ctypes import c_int
21
+ from ctypes import c_ssize_t
22
+ from ctypes import c_ulong
23
+ from ctypes import c_void_p
24
+ from ctypes import POINTER
25
+ from ctypes import py_object
26
+ from ctypes import Structure
27
+ from ctypes.wintypes import DWORD
28
+ from ctypes.wintypes import HANDLE
29
+ from ctypes.wintypes import LPCWSTR
30
+ from ctypes.wintypes import LPWSTR
31
+
32
+ from ._compat import _NonClosingTextIOWrapper
33
+
34
+ assert sys.platform == "win32"
35
+ import msvcrt # noqa: E402
36
+ from ctypes import windll # noqa: E402
37
+ from ctypes import WINFUNCTYPE # noqa: E402
38
+
39
+ c_ssize_p = POINTER(c_ssize_t)
40
+
41
+ kernel32 = windll.kernel32
42
+ GetStdHandle = kernel32.GetStdHandle
43
+ ReadConsoleW = kernel32.ReadConsoleW
44
+ WriteConsoleW = kernel32.WriteConsoleW
45
+ GetConsoleMode = kernel32.GetConsoleMode
46
+ GetLastError = kernel32.GetLastError
47
+ GetCommandLineW = WINFUNCTYPE(LPWSTR)(("GetCommandLineW", windll.kernel32))
48
+ CommandLineToArgvW = WINFUNCTYPE(POINTER(LPWSTR), LPCWSTR, POINTER(c_int))(
49
+ ("CommandLineToArgvW", windll.shell32)
50
+ )
51
+ LocalFree = WINFUNCTYPE(c_void_p, c_void_p)(("LocalFree", windll.kernel32))
52
+
53
+ STDIN_HANDLE = GetStdHandle(-10)
54
+ STDOUT_HANDLE = GetStdHandle(-11)
55
+ STDERR_HANDLE = GetStdHandle(-12)
56
+
57
+ PyBUF_SIMPLE = 0
58
+ PyBUF_WRITABLE = 1
59
+
60
+ ERROR_SUCCESS = 0
61
+ ERROR_NOT_ENOUGH_MEMORY = 8
62
+ ERROR_OPERATION_ABORTED = 995
63
+
64
+ STDIN_FILENO = 0
65
+ STDOUT_FILENO = 1
66
+ STDERR_FILENO = 2
67
+
68
+ EOF = b"\x1a"
69
+ MAX_BYTES_WRITTEN = 32767
70
+
71
+ if t.TYPE_CHECKING:
72
+ try:
73
+ # Using `typing_extensions.Buffer` instead of `collections.abc`
74
+ # on Windows for some reason does not have `Sized` implemented.
75
+ from collections.abc import Buffer # type: ignore
76
+ except ImportError:
77
+ from typing_extensions import Buffer
78
+
79
+ try:
80
+ from ctypes import pythonapi
81
+ except ImportError:
82
+ # On PyPy we cannot get buffers so our ability to operate here is
83
+ # severely limited.
84
+ get_buffer = None
85
+ else:
86
+
87
+ class Py_buffer(Structure):
88
+ _fields_ = [ # noqa: RUF012
89
+ ("buf", c_void_p),
90
+ ("obj", py_object),
91
+ ("len", c_ssize_t),
92
+ ("itemsize", c_ssize_t),
93
+ ("readonly", c_int),
94
+ ("ndim", c_int),
95
+ ("format", c_char_p),
96
+ ("shape", c_ssize_p),
97
+ ("strides", c_ssize_p),
98
+ ("suboffsets", c_ssize_p),
99
+ ("internal", c_void_p),
100
+ ]
101
+
102
+ PyObject_GetBuffer = pythonapi.PyObject_GetBuffer
103
+ PyBuffer_Release = pythonapi.PyBuffer_Release
104
+
105
+ def get_buffer(obj: Buffer, writable: bool = False) -> Array[c_char]:
106
+ buf = Py_buffer()
107
+ flags: int = PyBUF_WRITABLE if writable else PyBUF_SIMPLE
108
+ PyObject_GetBuffer(py_object(obj), byref(buf), flags)
109
+
110
+ try:
111
+ buffer_type = c_char * buf.len
112
+ out: Array[c_char] = buffer_type.from_address(buf.buf)
113
+ return out
114
+ finally:
115
+ PyBuffer_Release(byref(buf))
116
+
117
+
118
+ class _WindowsConsoleRawIOBase(io.RawIOBase):
119
+ def __init__(self, handle: int | None) -> None:
120
+ self.handle = handle
121
+
122
+ def isatty(self) -> t.Literal[True]:
123
+ super().isatty()
124
+ return True
125
+
126
+
127
+ class _WindowsConsoleReader(_WindowsConsoleRawIOBase):
128
+ def readable(self) -> t.Literal[True]:
129
+ return True
130
+
131
+ def readinto(self, b: Buffer) -> int:
132
+ bytes_to_be_read = len(b)
133
+ if not bytes_to_be_read:
134
+ return 0
135
+ elif bytes_to_be_read % 2:
136
+ raise ValueError(
137
+ "cannot read odd number of bytes from UTF-16-LE encoded console"
138
+ )
139
+
140
+ buffer = get_buffer(b, writable=True)
141
+ code_units_to_be_read = bytes_to_be_read // 2
142
+ code_units_read = c_ulong()
143
+
144
+ rv = ReadConsoleW(
145
+ HANDLE(self.handle),
146
+ buffer,
147
+ code_units_to_be_read,
148
+ byref(code_units_read),
149
+ None,
150
+ )
151
+ if GetLastError() == ERROR_OPERATION_ABORTED:
152
+ # wait for KeyboardInterrupt
153
+ time.sleep(0.1)
154
+ if not rv:
155
+ raise OSError(f"Windows error: {GetLastError()}")
156
+
157
+ if buffer[0] == EOF:
158
+ return 0
159
+ return 2 * code_units_read.value
160
+
161
+
162
+ class _WindowsConsoleWriter(_WindowsConsoleRawIOBase):
163
+ def writable(self) -> t.Literal[True]:
164
+ return True
165
+
166
+ @staticmethod
167
+ def _get_error_message(errno: int) -> str:
168
+ if errno == ERROR_SUCCESS:
169
+ return "ERROR_SUCCESS"
170
+ elif errno == ERROR_NOT_ENOUGH_MEMORY:
171
+ return "ERROR_NOT_ENOUGH_MEMORY"
172
+ return f"Windows error {errno}"
173
+
174
+ def write(self, b: Buffer) -> int:
175
+ bytes_to_be_written = len(b)
176
+ buf = get_buffer(b)
177
+ code_units_to_be_written = min(bytes_to_be_written, MAX_BYTES_WRITTEN) // 2
178
+ code_units_written = c_ulong()
179
+
180
+ WriteConsoleW(
181
+ HANDLE(self.handle),
182
+ buf,
183
+ code_units_to_be_written,
184
+ byref(code_units_written),
185
+ None,
186
+ )
187
+ bytes_written = 2 * code_units_written.value
188
+
189
+ if bytes_written == 0 and bytes_to_be_written > 0:
190
+ raise OSError(self._get_error_message(GetLastError()))
191
+ return bytes_written
192
+
193
+
194
+ class ConsoleStream:
195
+ def __init__(self, text_stream: t.TextIO, byte_stream: t.BinaryIO) -> None:
196
+ self._text_stream = text_stream
197
+ self.buffer = byte_stream
198
+
199
+ @property
200
+ def name(self) -> str:
201
+ return self.buffer.name
202
+
203
+ def write(self, x: t.AnyStr) -> int:
204
+ if isinstance(x, str):
205
+ return self._text_stream.write(x)
206
+ try:
207
+ self.flush()
208
+ except Exception:
209
+ pass
210
+ return self.buffer.write(x)
211
+
212
+ def writelines(self, lines: cabc.Iterable[t.AnyStr]) -> None:
213
+ for line in lines:
214
+ self.write(line)
215
+
216
+ def __getattr__(self, name: str) -> t.Any:
217
+ return getattr(self._text_stream, name)
218
+
219
+ def isatty(self) -> bool:
220
+ return self.buffer.isatty()
221
+
222
+ def __repr__(self) -> str:
223
+ return f"<ConsoleStream name={self.name!r} encoding={self.encoding!r}>"
224
+
225
+
226
+ def _get_text_stdin(buffer_stream: t.BinaryIO) -> t.TextIO:
227
+ text_stream = _NonClosingTextIOWrapper(
228
+ io.BufferedReader(_WindowsConsoleReader(STDIN_HANDLE)),
229
+ "utf-16-le",
230
+ "strict",
231
+ line_buffering=True,
232
+ )
233
+ return t.cast(t.TextIO, ConsoleStream(text_stream, buffer_stream))
234
+
235
+
236
+ def _get_text_stdout(buffer_stream: t.BinaryIO) -> t.TextIO:
237
+ text_stream = _NonClosingTextIOWrapper(
238
+ io.BufferedWriter(_WindowsConsoleWriter(STDOUT_HANDLE)),
239
+ "utf-16-le",
240
+ "strict",
241
+ line_buffering=True,
242
+ )
243
+ return t.cast(t.TextIO, ConsoleStream(text_stream, buffer_stream))
244
+
245
+
246
+ def _get_text_stderr(buffer_stream: t.BinaryIO) -> t.TextIO:
247
+ text_stream = _NonClosingTextIOWrapper(
248
+ io.BufferedWriter(_WindowsConsoleWriter(STDERR_HANDLE)),
249
+ "utf-16-le",
250
+ "strict",
251
+ line_buffering=True,
252
+ )
253
+ return t.cast(t.TextIO, ConsoleStream(text_stream, buffer_stream))
254
+
255
+
256
+ _stream_factories: cabc.Mapping[int, t.Callable[[t.BinaryIO], t.TextIO]] = {
257
+ 0: _get_text_stdin,
258
+ 1: _get_text_stdout,
259
+ 2: _get_text_stderr,
260
+ }
261
+
262
+
263
+ def _is_console(f: t.TextIO) -> bool:
264
+ if not hasattr(f, "fileno"):
265
+ return False
266
+
267
+ try:
268
+ fileno = f.fileno()
269
+ except (OSError, io.UnsupportedOperation):
270
+ return False
271
+
272
+ handle = msvcrt.get_osfhandle(fileno)
273
+ return bool(GetConsoleMode(handle, byref(DWORD())))
274
+
275
+
276
+ def _get_windows_console_stream(
277
+ f: t.TextIO, encoding: str | None, errors: str | None
278
+ ) -> t.TextIO | None:
279
+ if (
280
+ get_buffer is None
281
+ or encoding not in {"utf-16-le", None}
282
+ or errors not in {"strict", None}
283
+ or not _is_console(f)
284
+ ):
285
+ return None
286
+
287
+ func = _stream_factories.get(f.fileno())
288
+ if func is None:
289
+ return None
290
+
291
+ b = getattr(f, "buffer", None)
292
+
293
+ if b is None:
294
+ return None
295
+
296
+ return func(b)
lib/python3.12/site-packages/click/core.py ADDED
The diff for this file is too large to render. See raw diff
 
lib/python3.12/site-packages/click/decorators.py ADDED
@@ -0,0 +1,551 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import inspect
4
+ import typing as t
5
+ from functools import update_wrapper
6
+ from gettext import gettext as _
7
+
8
+ from .core import Argument
9
+ from .core import Command
10
+ from .core import Context
11
+ from .core import Group
12
+ from .core import Option
13
+ from .core import Parameter
14
+ from .globals import get_current_context
15
+ from .utils import echo
16
+
17
+ if t.TYPE_CHECKING:
18
+ import typing_extensions as te
19
+
20
+ P = te.ParamSpec("P")
21
+
22
+ R = t.TypeVar("R")
23
+ T = t.TypeVar("T")
24
+ _AnyCallable = t.Callable[..., t.Any]
25
+ FC = t.TypeVar("FC", bound="_AnyCallable | Command")
26
+
27
+
28
+ def pass_context(f: t.Callable[te.Concatenate[Context, P], R]) -> t.Callable[P, R]:
29
+ """Marks a callback as wanting to receive the current context
30
+ object as first argument.
31
+ """
32
+
33
+ def new_func(*args: P.args, **kwargs: P.kwargs) -> R:
34
+ return f(get_current_context(), *args, **kwargs)
35
+
36
+ return update_wrapper(new_func, f)
37
+
38
+
39
+ def pass_obj(f: t.Callable[te.Concatenate[T, P], R]) -> t.Callable[P, R]:
40
+ """Similar to :func:`pass_context`, but only pass the object on the
41
+ context onwards (:attr:`Context.obj`). This is useful if that object
42
+ represents the state of a nested system.
43
+ """
44
+
45
+ def new_func(*args: P.args, **kwargs: P.kwargs) -> R:
46
+ return f(get_current_context().obj, *args, **kwargs)
47
+
48
+ return update_wrapper(new_func, f)
49
+
50
+
51
+ def make_pass_decorator(
52
+ object_type: type[T], ensure: bool = False
53
+ ) -> t.Callable[[t.Callable[te.Concatenate[T, P], R]], t.Callable[P, R]]:
54
+ """Given an object type this creates a decorator that will work
55
+ similar to :func:`pass_obj` but instead of passing the object of the
56
+ current context, it will find the innermost context of type
57
+ :func:`object_type`.
58
+
59
+ This generates a decorator that works roughly like this::
60
+
61
+ from functools import update_wrapper
62
+
63
+ def decorator(f):
64
+ @pass_context
65
+ def new_func(ctx, *args, **kwargs):
66
+ obj = ctx.find_object(object_type)
67
+ return ctx.invoke(f, obj, *args, **kwargs)
68
+ return update_wrapper(new_func, f)
69
+ return decorator
70
+
71
+ :param object_type: the type of the object to pass.
72
+ :param ensure: if set to `True`, a new object will be created and
73
+ remembered on the context if it's not there yet.
74
+ """
75
+
76
+ def decorator(f: t.Callable[te.Concatenate[T, P], R]) -> t.Callable[P, R]:
77
+ def new_func(*args: P.args, **kwargs: P.kwargs) -> R:
78
+ ctx = get_current_context()
79
+
80
+ obj: T | None
81
+ if ensure:
82
+ obj = ctx.ensure_object(object_type)
83
+ else:
84
+ obj = ctx.find_object(object_type)
85
+
86
+ if obj is None:
87
+ raise RuntimeError(
88
+ "Managed to invoke callback without a context"
89
+ f" object of type {object_type.__name__!r}"
90
+ " existing."
91
+ )
92
+
93
+ return ctx.invoke(f, obj, *args, **kwargs)
94
+
95
+ return update_wrapper(new_func, f)
96
+
97
+ return decorator
98
+
99
+
100
+ def pass_meta_key(
101
+ key: str, *, doc_description: str | None = None
102
+ ) -> t.Callable[[t.Callable[te.Concatenate[T, P], R]], t.Callable[P, R]]:
103
+ """Create a decorator that passes a key from
104
+ :attr:`click.Context.meta` as the first argument to the decorated
105
+ function.
106
+
107
+ :param key: Key in ``Context.meta`` to pass.
108
+ :param doc_description: Description of the object being passed,
109
+ inserted into the decorator's docstring. Defaults to "the 'key'
110
+ key from Context.meta".
111
+
112
+ .. versionadded:: 8.0
113
+ """
114
+
115
+ def decorator(f: t.Callable[te.Concatenate[T, P], R]) -> t.Callable[P, R]:
116
+ def new_func(*args: P.args, **kwargs: P.kwargs) -> R:
117
+ ctx = get_current_context()
118
+ obj = ctx.meta[key]
119
+ return ctx.invoke(f, obj, *args, **kwargs)
120
+
121
+ return update_wrapper(new_func, f)
122
+
123
+ if doc_description is None:
124
+ doc_description = f"the {key!r} key from :attr:`click.Context.meta`"
125
+
126
+ decorator.__doc__ = (
127
+ f"Decorator that passes {doc_description} as the first argument"
128
+ " to the decorated function."
129
+ )
130
+ return decorator
131
+
132
+
133
+ CmdType = t.TypeVar("CmdType", bound=Command)
134
+
135
+
136
+ # variant: no call, directly as decorator for a function.
137
+ @t.overload
138
+ def command(name: _AnyCallable) -> Command: ...
139
+
140
+
141
+ # variant: with positional name and with positional or keyword cls argument:
142
+ # @command(namearg, CommandCls, ...) or @command(namearg, cls=CommandCls, ...)
143
+ @t.overload
144
+ def command(
145
+ name: str | None,
146
+ cls: type[CmdType],
147
+ **attrs: t.Any,
148
+ ) -> t.Callable[[_AnyCallable], CmdType]: ...
149
+
150
+
151
+ # variant: name omitted, cls _must_ be a keyword argument, @command(cls=CommandCls, ...)
152
+ @t.overload
153
+ def command(
154
+ name: None = None,
155
+ *,
156
+ cls: type[CmdType],
157
+ **attrs: t.Any,
158
+ ) -> t.Callable[[_AnyCallable], CmdType]: ...
159
+
160
+
161
+ # variant: with optional string name, no cls argument provided.
162
+ @t.overload
163
+ def command(
164
+ name: str | None = ..., cls: None = None, **attrs: t.Any
165
+ ) -> t.Callable[[_AnyCallable], Command]: ...
166
+
167
+
168
+ def command(
169
+ name: str | _AnyCallable | None = None,
170
+ cls: type[CmdType] | None = None,
171
+ **attrs: t.Any,
172
+ ) -> Command | t.Callable[[_AnyCallable], Command | CmdType]:
173
+ r"""Creates a new :class:`Command` and uses the decorated function as
174
+ callback. This will also automatically attach all decorated
175
+ :func:`option`\s and :func:`argument`\s as parameters to the command.
176
+
177
+ The name of the command defaults to the name of the function, converted to
178
+ lowercase, with underscores ``_`` replaced by dashes ``-``, and the suffixes
179
+ ``_command``, ``_cmd``, ``_group``, and ``_grp`` are removed. For example,
180
+ ``init_data_command`` becomes ``init-data``.
181
+
182
+ All keyword arguments are forwarded to the underlying command class.
183
+ For the ``params`` argument, any decorated params are appended to
184
+ the end of the list.
185
+
186
+ Once decorated the function turns into a :class:`Command` instance
187
+ that can be invoked as a command line utility or be attached to a
188
+ command :class:`Group`.
189
+
190
+ :param name: The name of the command. Defaults to modifying the function's
191
+ name as described above.
192
+ :param cls: The command class to create. Defaults to :class:`Command`.
193
+
194
+ .. versionchanged:: 8.2
195
+ The suffixes ``_command``, ``_cmd``, ``_group``, and ``_grp`` are
196
+ removed when generating the name.
197
+
198
+ .. versionchanged:: 8.1
199
+ This decorator can be applied without parentheses.
200
+
201
+ .. versionchanged:: 8.1
202
+ The ``params`` argument can be used. Decorated params are
203
+ appended to the end of the list.
204
+ """
205
+
206
+ func: t.Callable[[_AnyCallable], t.Any] | None = None
207
+
208
+ if callable(name):
209
+ func = name
210
+ name = None
211
+ assert cls is None, "Use 'command(cls=cls)(callable)' to specify a class."
212
+ assert not attrs, "Use 'command(**kwargs)(callable)' to provide arguments."
213
+
214
+ if cls is None:
215
+ cls = t.cast("type[CmdType]", Command)
216
+
217
+ def decorator(f: _AnyCallable) -> CmdType:
218
+ if isinstance(f, Command):
219
+ raise TypeError("Attempted to convert a callback into a command twice.")
220
+
221
+ attr_params = attrs.pop("params", None)
222
+ params = attr_params if attr_params is not None else []
223
+
224
+ try:
225
+ decorator_params = f.__click_params__ # type: ignore
226
+ except AttributeError:
227
+ pass
228
+ else:
229
+ del f.__click_params__ # type: ignore
230
+ params.extend(reversed(decorator_params))
231
+
232
+ if attrs.get("help") is None:
233
+ attrs["help"] = f.__doc__
234
+
235
+ if t.TYPE_CHECKING:
236
+ assert cls is not None
237
+ assert not callable(name)
238
+
239
+ if name is not None:
240
+ cmd_name = name
241
+ else:
242
+ cmd_name = f.__name__.lower().replace("_", "-")
243
+ cmd_left, sep, suffix = cmd_name.rpartition("-")
244
+
245
+ if sep and suffix in {"command", "cmd", "group", "grp"}:
246
+ cmd_name = cmd_left
247
+
248
+ cmd = cls(name=cmd_name, callback=f, params=params, **attrs)
249
+ cmd.__doc__ = f.__doc__
250
+ return cmd
251
+
252
+ if func is not None:
253
+ return decorator(func)
254
+
255
+ return decorator
256
+
257
+
258
+ GrpType = t.TypeVar("GrpType", bound=Group)
259
+
260
+
261
+ # variant: no call, directly as decorator for a function.
262
+ @t.overload
263
+ def group(name: _AnyCallable) -> Group: ...
264
+
265
+
266
+ # variant: with positional name and with positional or keyword cls argument:
267
+ # @group(namearg, GroupCls, ...) or @group(namearg, cls=GroupCls, ...)
268
+ @t.overload
269
+ def group(
270
+ name: str | None,
271
+ cls: type[GrpType],
272
+ **attrs: t.Any,
273
+ ) -> t.Callable[[_AnyCallable], GrpType]: ...
274
+
275
+
276
+ # variant: name omitted, cls _must_ be a keyword argument, @group(cmd=GroupCls, ...)
277
+ @t.overload
278
+ def group(
279
+ name: None = None,
280
+ *,
281
+ cls: type[GrpType],
282
+ **attrs: t.Any,
283
+ ) -> t.Callable[[_AnyCallable], GrpType]: ...
284
+
285
+
286
+ # variant: with optional string name, no cls argument provided.
287
+ @t.overload
288
+ def group(
289
+ name: str | None = ..., cls: None = None, **attrs: t.Any
290
+ ) -> t.Callable[[_AnyCallable], Group]: ...
291
+
292
+
293
+ def group(
294
+ name: str | _AnyCallable | None = None,
295
+ cls: type[GrpType] | None = None,
296
+ **attrs: t.Any,
297
+ ) -> Group | t.Callable[[_AnyCallable], Group | GrpType]:
298
+ """Creates a new :class:`Group` with a function as callback. This
299
+ works otherwise the same as :func:`command` just that the `cls`
300
+ parameter is set to :class:`Group`.
301
+
302
+ .. versionchanged:: 8.1
303
+ This decorator can be applied without parentheses.
304
+ """
305
+ if cls is None:
306
+ cls = t.cast("type[GrpType]", Group)
307
+
308
+ if callable(name):
309
+ return command(cls=cls, **attrs)(name)
310
+
311
+ return command(name, cls, **attrs)
312
+
313
+
314
+ def _param_memo(f: t.Callable[..., t.Any], param: Parameter) -> None:
315
+ if isinstance(f, Command):
316
+ f.params.append(param)
317
+ else:
318
+ if not hasattr(f, "__click_params__"):
319
+ f.__click_params__ = [] # type: ignore
320
+
321
+ f.__click_params__.append(param) # type: ignore
322
+
323
+
324
+ def argument(
325
+ *param_decls: str, cls: type[Argument] | None = None, **attrs: t.Any
326
+ ) -> t.Callable[[FC], FC]:
327
+ """Attaches an argument to the command. All positional arguments are
328
+ passed as parameter declarations to :class:`Argument`; all keyword
329
+ arguments are forwarded unchanged (except ``cls``).
330
+ This is equivalent to creating an :class:`Argument` instance manually
331
+ and attaching it to the :attr:`Command.params` list.
332
+
333
+ For the default argument class, refer to :class:`Argument` and
334
+ :class:`Parameter` for descriptions of parameters.
335
+
336
+ :param cls: the argument class to instantiate. This defaults to
337
+ :class:`Argument`.
338
+ :param param_decls: Passed as positional arguments to the constructor of
339
+ ``cls``.
340
+ :param attrs: Passed as keyword arguments to the constructor of ``cls``.
341
+ """
342
+ if cls is None:
343
+ cls = Argument
344
+
345
+ def decorator(f: FC) -> FC:
346
+ _param_memo(f, cls(param_decls, **attrs))
347
+ return f
348
+
349
+ return decorator
350
+
351
+
352
+ def option(
353
+ *param_decls: str, cls: type[Option] | None = None, **attrs: t.Any
354
+ ) -> t.Callable[[FC], FC]:
355
+ """Attaches an option to the command. All positional arguments are
356
+ passed as parameter declarations to :class:`Option`; all keyword
357
+ arguments are forwarded unchanged (except ``cls``).
358
+ This is equivalent to creating an :class:`Option` instance manually
359
+ and attaching it to the :attr:`Command.params` list.
360
+
361
+ For the default option class, refer to :class:`Option` and
362
+ :class:`Parameter` for descriptions of parameters.
363
+
364
+ :param cls: the option class to instantiate. This defaults to
365
+ :class:`Option`.
366
+ :param param_decls: Passed as positional arguments to the constructor of
367
+ ``cls``.
368
+ :param attrs: Passed as keyword arguments to the constructor of ``cls``.
369
+ """
370
+ if cls is None:
371
+ cls = Option
372
+
373
+ def decorator(f: FC) -> FC:
374
+ _param_memo(f, cls(param_decls, **attrs))
375
+ return f
376
+
377
+ return decorator
378
+
379
+
380
+ def confirmation_option(*param_decls: str, **kwargs: t.Any) -> t.Callable[[FC], FC]:
381
+ """Add a ``--yes`` option which shows a prompt before continuing if
382
+ not passed. If the prompt is declined, the program will exit.
383
+
384
+ :param param_decls: One or more option names. Defaults to the single
385
+ value ``"--yes"``.
386
+ :param kwargs: Extra arguments are passed to :func:`option`.
387
+ """
388
+
389
+ def callback(ctx: Context, param: Parameter, value: bool) -> None:
390
+ if not value:
391
+ ctx.abort()
392
+
393
+ if not param_decls:
394
+ param_decls = ("--yes",)
395
+
396
+ kwargs.setdefault("is_flag", True)
397
+ kwargs.setdefault("callback", callback)
398
+ kwargs.setdefault("expose_value", False)
399
+ kwargs.setdefault("prompt", "Do you want to continue?")
400
+ kwargs.setdefault("help", "Confirm the action without prompting.")
401
+ return option(*param_decls, **kwargs)
402
+
403
+
404
+ def password_option(*param_decls: str, **kwargs: t.Any) -> t.Callable[[FC], FC]:
405
+ """Add a ``--password`` option which prompts for a password, hiding
406
+ input and asking to enter the value again for confirmation.
407
+
408
+ :param param_decls: One or more option names. Defaults to the single
409
+ value ``"--password"``.
410
+ :param kwargs: Extra arguments are passed to :func:`option`.
411
+ """
412
+ if not param_decls:
413
+ param_decls = ("--password",)
414
+
415
+ kwargs.setdefault("prompt", True)
416
+ kwargs.setdefault("confirmation_prompt", True)
417
+ kwargs.setdefault("hide_input", True)
418
+ return option(*param_decls, **kwargs)
419
+
420
+
421
+ def version_option(
422
+ version: str | None = None,
423
+ *param_decls: str,
424
+ package_name: str | None = None,
425
+ prog_name: str | None = None,
426
+ message: str | None = None,
427
+ **kwargs: t.Any,
428
+ ) -> t.Callable[[FC], FC]:
429
+ """Add a ``--version`` option which immediately prints the version
430
+ number and exits the program.
431
+
432
+ If ``version`` is not provided, Click will try to detect it using
433
+ :func:`importlib.metadata.version` to get the version for the
434
+ ``package_name``.
435
+
436
+ If ``package_name`` is not provided, Click will try to detect it by
437
+ inspecting the stack frames. This will be used to detect the
438
+ version, so it must match the name of the installed package.
439
+
440
+ :param version: The version number to show. If not provided, Click
441
+ will try to detect it.
442
+ :param param_decls: One or more option names. Defaults to the single
443
+ value ``"--version"``.
444
+ :param package_name: The package name to detect the version from. If
445
+ not provided, Click will try to detect it.
446
+ :param prog_name: The name of the CLI to show in the message. If not
447
+ provided, it will be detected from the command.
448
+ :param message: The message to show. The values ``%(prog)s``,
449
+ ``%(package)s``, and ``%(version)s`` are available. Defaults to
450
+ ``"%(prog)s, version %(version)s"``.
451
+ :param kwargs: Extra arguments are passed to :func:`option`.
452
+ :raise RuntimeError: ``version`` could not be detected.
453
+
454
+ .. versionchanged:: 8.0
455
+ Add the ``package_name`` parameter, and the ``%(package)s``
456
+ value for messages.
457
+
458
+ .. versionchanged:: 8.0
459
+ Use :mod:`importlib.metadata` instead of ``pkg_resources``. The
460
+ version is detected based on the package name, not the entry
461
+ point name. The Python package name must match the installed
462
+ package name, or be passed with ``package_name=``.
463
+ """
464
+ if message is None:
465
+ message = _("%(prog)s, version %(version)s")
466
+
467
+ if version is None and package_name is None:
468
+ frame = inspect.currentframe()
469
+ f_back = frame.f_back if frame is not None else None
470
+ f_globals = f_back.f_globals if f_back is not None else None
471
+ # break reference cycle
472
+ # https://docs.python.org/3/library/inspect.html#the-interpreter-stack
473
+ del frame
474
+
475
+ if f_globals is not None:
476
+ package_name = f_globals.get("__name__")
477
+
478
+ if package_name == "__main__":
479
+ package_name = f_globals.get("__package__")
480
+
481
+ if package_name:
482
+ package_name = package_name.partition(".")[0]
483
+
484
+ def callback(ctx: Context, param: Parameter, value: bool) -> None:
485
+ if not value or ctx.resilient_parsing:
486
+ return
487
+
488
+ nonlocal prog_name
489
+ nonlocal version
490
+
491
+ if prog_name is None:
492
+ prog_name = ctx.find_root().info_name
493
+
494
+ if version is None and package_name is not None:
495
+ import importlib.metadata
496
+
497
+ try:
498
+ version = importlib.metadata.version(package_name)
499
+ except importlib.metadata.PackageNotFoundError:
500
+ raise RuntimeError(
501
+ f"{package_name!r} is not installed. Try passing"
502
+ " 'package_name' instead."
503
+ ) from None
504
+
505
+ if version is None:
506
+ raise RuntimeError(
507
+ f"Could not determine the version for {package_name!r} automatically."
508
+ )
509
+
510
+ echo(
511
+ message % {"prog": prog_name, "package": package_name, "version": version},
512
+ color=ctx.color,
513
+ )
514
+ ctx.exit()
515
+
516
+ if not param_decls:
517
+ param_decls = ("--version",)
518
+
519
+ kwargs.setdefault("is_flag", True)
520
+ kwargs.setdefault("expose_value", False)
521
+ kwargs.setdefault("is_eager", True)
522
+ kwargs.setdefault("help", _("Show the version and exit."))
523
+ kwargs["callback"] = callback
524
+ return option(*param_decls, **kwargs)
525
+
526
+
527
+ def help_option(*param_decls: str, **kwargs: t.Any) -> t.Callable[[FC], FC]:
528
+ """Pre-configured ``--help`` option which immediately prints the help page
529
+ and exits the program.
530
+
531
+ :param param_decls: One or more option names. Defaults to the single
532
+ value ``"--help"``.
533
+ :param kwargs: Extra arguments are passed to :func:`option`.
534
+ """
535
+
536
+ def show_help(ctx: Context, param: Parameter, value: bool) -> None:
537
+ """Callback that print the help page on ``<stdout>`` and exits."""
538
+ if value and not ctx.resilient_parsing:
539
+ echo(ctx.get_help(), color=ctx.color)
540
+ ctx.exit()
541
+
542
+ if not param_decls:
543
+ param_decls = ("--help",)
544
+
545
+ kwargs.setdefault("is_flag", True)
546
+ kwargs.setdefault("expose_value", False)
547
+ kwargs.setdefault("is_eager", True)
548
+ kwargs.setdefault("help", _("Show this message and exit."))
549
+ kwargs.setdefault("callback", show_help)
550
+
551
+ return option(*param_decls, **kwargs)
lib/python3.12/site-packages/click/exceptions.py ADDED
@@ -0,0 +1,308 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import collections.abc as cabc
4
+ import typing as t
5
+ from gettext import gettext as _
6
+ from gettext import ngettext
7
+
8
+ from ._compat import get_text_stderr
9
+ from .globals import resolve_color_default
10
+ from .utils import echo
11
+ from .utils import format_filename
12
+
13
+ if t.TYPE_CHECKING:
14
+ from .core import Command
15
+ from .core import Context
16
+ from .core import Parameter
17
+
18
+
19
+ def _join_param_hints(param_hint: cabc.Sequence[str] | str | None) -> str | None:
20
+ if param_hint is not None and not isinstance(param_hint, str):
21
+ return " / ".join(repr(x) for x in param_hint)
22
+
23
+ return param_hint
24
+
25
+
26
+ class ClickException(Exception):
27
+ """An exception that Click can handle and show to the user."""
28
+
29
+ #: The exit code for this exception.
30
+ exit_code = 1
31
+
32
+ def __init__(self, message: str) -> None:
33
+ super().__init__(message)
34
+ # The context will be removed by the time we print the message, so cache
35
+ # the color settings here to be used later on (in `show`)
36
+ self.show_color: bool | None = resolve_color_default()
37
+ self.message = message
38
+
39
+ def format_message(self) -> str:
40
+ return self.message
41
+
42
+ def __str__(self) -> str:
43
+ return self.message
44
+
45
+ def show(self, file: t.IO[t.Any] | None = None) -> None:
46
+ if file is None:
47
+ file = get_text_stderr()
48
+
49
+ echo(
50
+ _("Error: {message}").format(message=self.format_message()),
51
+ file=file,
52
+ color=self.show_color,
53
+ )
54
+
55
+
56
+ class UsageError(ClickException):
57
+ """An internal exception that signals a usage error. This typically
58
+ aborts any further handling.
59
+
60
+ :param message: the error message to display.
61
+ :param ctx: optionally the context that caused this error. Click will
62
+ fill in the context automatically in some situations.
63
+ """
64
+
65
+ exit_code = 2
66
+
67
+ def __init__(self, message: str, ctx: Context | None = None) -> None:
68
+ super().__init__(message)
69
+ self.ctx = ctx
70
+ self.cmd: Command | None = self.ctx.command if self.ctx else None
71
+
72
+ def show(self, file: t.IO[t.Any] | None = None) -> None:
73
+ if file is None:
74
+ file = get_text_stderr()
75
+ color = None
76
+ hint = ""
77
+ if (
78
+ self.ctx is not None
79
+ and self.ctx.command.get_help_option(self.ctx) is not None
80
+ ):
81
+ hint = _("Try '{command} {option}' for help.").format(
82
+ command=self.ctx.command_path, option=self.ctx.help_option_names[0]
83
+ )
84
+ hint = f"{hint}\n"
85
+ if self.ctx is not None:
86
+ color = self.ctx.color
87
+ echo(f"{self.ctx.get_usage()}\n{hint}", file=file, color=color)
88
+ echo(
89
+ _("Error: {message}").format(message=self.format_message()),
90
+ file=file,
91
+ color=color,
92
+ )
93
+
94
+
95
+ class BadParameter(UsageError):
96
+ """An exception that formats out a standardized error message for a
97
+ bad parameter. This is useful when thrown from a callback or type as
98
+ Click will attach contextual information to it (for instance, which
99
+ parameter it is).
100
+
101
+ .. versionadded:: 2.0
102
+
103
+ :param param: the parameter object that caused this error. This can
104
+ be left out, and Click will attach this info itself
105
+ if possible.
106
+ :param param_hint: a string that shows up as parameter name. This
107
+ can be used as alternative to `param` in cases
108
+ where custom validation should happen. If it is
109
+ a string it's used as such, if it's a list then
110
+ each item is quoted and separated.
111
+ """
112
+
113
+ def __init__(
114
+ self,
115
+ message: str,
116
+ ctx: Context | None = None,
117
+ param: Parameter | None = None,
118
+ param_hint: str | None = None,
119
+ ) -> None:
120
+ super().__init__(message, ctx)
121
+ self.param = param
122
+ self.param_hint = param_hint
123
+
124
+ def format_message(self) -> str:
125
+ if self.param_hint is not None:
126
+ param_hint = self.param_hint
127
+ elif self.param is not None:
128
+ param_hint = self.param.get_error_hint(self.ctx) # type: ignore
129
+ else:
130
+ return _("Invalid value: {message}").format(message=self.message)
131
+
132
+ return _("Invalid value for {param_hint}: {message}").format(
133
+ param_hint=_join_param_hints(param_hint), message=self.message
134
+ )
135
+
136
+
137
+ class MissingParameter(BadParameter):
138
+ """Raised if click required an option or argument but it was not
139
+ provided when invoking the script.
140
+
141
+ .. versionadded:: 4.0
142
+
143
+ :param param_type: a string that indicates the type of the parameter.
144
+ The default is to inherit the parameter type from
145
+ the given `param`. Valid values are ``'parameter'``,
146
+ ``'option'`` or ``'argument'``.
147
+ """
148
+
149
+ def __init__(
150
+ self,
151
+ message: str | None = None,
152
+ ctx: Context | None = None,
153
+ param: Parameter | None = None,
154
+ param_hint: str | None = None,
155
+ param_type: str | None = None,
156
+ ) -> None:
157
+ super().__init__(message or "", ctx, param, param_hint)
158
+ self.param_type = param_type
159
+
160
+ def format_message(self) -> str:
161
+ if self.param_hint is not None:
162
+ param_hint: str | None = self.param_hint
163
+ elif self.param is not None:
164
+ param_hint = self.param.get_error_hint(self.ctx) # type: ignore
165
+ else:
166
+ param_hint = None
167
+
168
+ param_hint = _join_param_hints(param_hint)
169
+ param_hint = f" {param_hint}" if param_hint else ""
170
+
171
+ param_type = self.param_type
172
+ if param_type is None and self.param is not None:
173
+ param_type = self.param.param_type_name
174
+
175
+ msg = self.message
176
+ if self.param is not None:
177
+ msg_extra = self.param.type.get_missing_message(
178
+ param=self.param, ctx=self.ctx
179
+ )
180
+ if msg_extra:
181
+ if msg:
182
+ msg += f". {msg_extra}"
183
+ else:
184
+ msg = msg_extra
185
+
186
+ msg = f" {msg}" if msg else ""
187
+
188
+ # Translate param_type for known types.
189
+ if param_type == "argument":
190
+ missing = _("Missing argument")
191
+ elif param_type == "option":
192
+ missing = _("Missing option")
193
+ elif param_type == "parameter":
194
+ missing = _("Missing parameter")
195
+ else:
196
+ missing = _("Missing {param_type}").format(param_type=param_type)
197
+
198
+ return f"{missing}{param_hint}.{msg}"
199
+
200
+ def __str__(self) -> str:
201
+ if not self.message:
202
+ param_name = self.param.name if self.param else None
203
+ return _("Missing parameter: {param_name}").format(param_name=param_name)
204
+ else:
205
+ return self.message
206
+
207
+
208
+ class NoSuchOption(UsageError):
209
+ """Raised if click attempted to handle an option that does not
210
+ exist.
211
+
212
+ .. versionadded:: 4.0
213
+ """
214
+
215
+ def __init__(
216
+ self,
217
+ option_name: str,
218
+ message: str | None = None,
219
+ possibilities: cabc.Sequence[str] | None = None,
220
+ ctx: Context | None = None,
221
+ ) -> None:
222
+ if message is None:
223
+ message = _("No such option: {name}").format(name=option_name)
224
+
225
+ super().__init__(message, ctx)
226
+ self.option_name = option_name
227
+ self.possibilities = possibilities
228
+
229
+ def format_message(self) -> str:
230
+ if not self.possibilities:
231
+ return self.message
232
+
233
+ possibility_str = ", ".join(sorted(self.possibilities))
234
+ suggest = ngettext(
235
+ "Did you mean {possibility}?",
236
+ "(Possible options: {possibilities})",
237
+ len(self.possibilities),
238
+ ).format(possibility=possibility_str, possibilities=possibility_str)
239
+ return f"{self.message} {suggest}"
240
+
241
+
242
+ class BadOptionUsage(UsageError):
243
+ """Raised if an option is generally supplied but the use of the option
244
+ was incorrect. This is for instance raised if the number of arguments
245
+ for an option is not correct.
246
+
247
+ .. versionadded:: 4.0
248
+
249
+ :param option_name: the name of the option being used incorrectly.
250
+ """
251
+
252
+ def __init__(
253
+ self, option_name: str, message: str, ctx: Context | None = None
254
+ ) -> None:
255
+ super().__init__(message, ctx)
256
+ self.option_name = option_name
257
+
258
+
259
+ class BadArgumentUsage(UsageError):
260
+ """Raised if an argument is generally supplied but the use of the argument
261
+ was incorrect. This is for instance raised if the number of values
262
+ for an argument is not correct.
263
+
264
+ .. versionadded:: 6.0
265
+ """
266
+
267
+
268
+ class NoArgsIsHelpError(UsageError):
269
+ def __init__(self, ctx: Context) -> None:
270
+ self.ctx: Context
271
+ super().__init__(ctx.get_help(), ctx=ctx)
272
+
273
+ def show(self, file: t.IO[t.Any] | None = None) -> None:
274
+ echo(self.format_message(), file=file, err=True, color=self.ctx.color)
275
+
276
+
277
+ class FileError(ClickException):
278
+ """Raised if a file cannot be opened."""
279
+
280
+ def __init__(self, filename: str, hint: str | None = None) -> None:
281
+ if hint is None:
282
+ hint = _("unknown error")
283
+
284
+ super().__init__(hint)
285
+ self.ui_filename: str = format_filename(filename)
286
+ self.filename = filename
287
+
288
+ def format_message(self) -> str:
289
+ return _("Could not open file {filename!r}: {message}").format(
290
+ filename=self.ui_filename, message=self.message
291
+ )
292
+
293
+
294
+ class Abort(RuntimeError):
295
+ """An internal signalling exception that signals Click to abort."""
296
+
297
+
298
+ class Exit(RuntimeError):
299
+ """An exception that indicates that the application should exit with some
300
+ status code.
301
+
302
+ :param code: the status code to exit with.
303
+ """
304
+
305
+ __slots__ = ("exit_code",)
306
+
307
+ def __init__(self, code: int = 0) -> None:
308
+ self.exit_code: int = code
lib/python3.12/site-packages/click/formatting.py ADDED
@@ -0,0 +1,301 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import collections.abc as cabc
4
+ from contextlib import contextmanager
5
+ from gettext import gettext as _
6
+
7
+ from ._compat import term_len
8
+ from .parser import _split_opt
9
+
10
+ # Can force a width. This is used by the test system
11
+ FORCED_WIDTH: int | None = None
12
+
13
+
14
+ def measure_table(rows: cabc.Iterable[tuple[str, str]]) -> tuple[int, ...]:
15
+ widths: dict[int, int] = {}
16
+
17
+ for row in rows:
18
+ for idx, col in enumerate(row):
19
+ widths[idx] = max(widths.get(idx, 0), term_len(col))
20
+
21
+ return tuple(y for x, y in sorted(widths.items()))
22
+
23
+
24
+ def iter_rows(
25
+ rows: cabc.Iterable[tuple[str, str]], col_count: int
26
+ ) -> cabc.Iterator[tuple[str, ...]]:
27
+ for row in rows:
28
+ yield row + ("",) * (col_count - len(row))
29
+
30
+
31
+ def wrap_text(
32
+ text: str,
33
+ width: int = 78,
34
+ initial_indent: str = "",
35
+ subsequent_indent: str = "",
36
+ preserve_paragraphs: bool = False,
37
+ ) -> str:
38
+ """A helper function that intelligently wraps text. By default, it
39
+ assumes that it operates on a single paragraph of text but if the
40
+ `preserve_paragraphs` parameter is provided it will intelligently
41
+ handle paragraphs (defined by two empty lines).
42
+
43
+ If paragraphs are handled, a paragraph can be prefixed with an empty
44
+ line containing the ``\\b`` character (``\\x08``) to indicate that
45
+ no rewrapping should happen in that block.
46
+
47
+ :param text: the text that should be rewrapped.
48
+ :param width: the maximum width for the text.
49
+ :param initial_indent: the initial indent that should be placed on the
50
+ first line as a string.
51
+ :param subsequent_indent: the indent string that should be placed on
52
+ each consecutive line.
53
+ :param preserve_paragraphs: if this flag is set then the wrapping will
54
+ intelligently handle paragraphs.
55
+ """
56
+ from ._textwrap import TextWrapper
57
+
58
+ text = text.expandtabs()
59
+ wrapper = TextWrapper(
60
+ width,
61
+ initial_indent=initial_indent,
62
+ subsequent_indent=subsequent_indent,
63
+ replace_whitespace=False,
64
+ )
65
+ if not preserve_paragraphs:
66
+ return wrapper.fill(text)
67
+
68
+ p: list[tuple[int, bool, str]] = []
69
+ buf: list[str] = []
70
+ indent = None
71
+
72
+ def _flush_par() -> None:
73
+ if not buf:
74
+ return
75
+ if buf[0].strip() == "\b":
76
+ p.append((indent or 0, True, "\n".join(buf[1:])))
77
+ else:
78
+ p.append((indent or 0, False, " ".join(buf)))
79
+ del buf[:]
80
+
81
+ for line in text.splitlines():
82
+ if not line:
83
+ _flush_par()
84
+ indent = None
85
+ else:
86
+ if indent is None:
87
+ orig_len = term_len(line)
88
+ line = line.lstrip()
89
+ indent = orig_len - term_len(line)
90
+ buf.append(line)
91
+ _flush_par()
92
+
93
+ rv = []
94
+ for indent, raw, text in p:
95
+ with wrapper.extra_indent(" " * indent):
96
+ if raw:
97
+ rv.append(wrapper.indent_only(text))
98
+ else:
99
+ rv.append(wrapper.fill(text))
100
+
101
+ return "\n\n".join(rv)
102
+
103
+
104
+ class HelpFormatter:
105
+ """This class helps with formatting text-based help pages. It's
106
+ usually just needed for very special internal cases, but it's also
107
+ exposed so that developers can write their own fancy outputs.
108
+
109
+ At present, it always writes into memory.
110
+
111
+ :param indent_increment: the additional increment for each level.
112
+ :param width: the width for the text. This defaults to the terminal
113
+ width clamped to a maximum of 78.
114
+ """
115
+
116
+ def __init__(
117
+ self,
118
+ indent_increment: int = 2,
119
+ width: int | None = None,
120
+ max_width: int | None = None,
121
+ ) -> None:
122
+ import shutil
123
+
124
+ self.indent_increment = indent_increment
125
+ if max_width is None:
126
+ max_width = 80
127
+ if width is None:
128
+ width = FORCED_WIDTH
129
+ if width is None:
130
+ width = max(min(shutil.get_terminal_size().columns, max_width) - 2, 50)
131
+ self.width = width
132
+ self.current_indent: int = 0
133
+ self.buffer: list[str] = []
134
+
135
+ def write(self, string: str) -> None:
136
+ """Writes a unicode string into the internal buffer."""
137
+ self.buffer.append(string)
138
+
139
+ def indent(self) -> None:
140
+ """Increases the indentation."""
141
+ self.current_indent += self.indent_increment
142
+
143
+ def dedent(self) -> None:
144
+ """Decreases the indentation."""
145
+ self.current_indent -= self.indent_increment
146
+
147
+ def write_usage(self, prog: str, args: str = "", prefix: str | None = None) -> None:
148
+ """Writes a usage line into the buffer.
149
+
150
+ :param prog: the program name.
151
+ :param args: whitespace separated list of arguments.
152
+ :param prefix: The prefix for the first line. Defaults to
153
+ ``"Usage: "``.
154
+ """
155
+ if prefix is None:
156
+ prefix = f"{_('Usage:')} "
157
+
158
+ usage_prefix = f"{prefix:>{self.current_indent}}{prog} "
159
+ text_width = self.width - self.current_indent
160
+
161
+ if text_width >= (term_len(usage_prefix) + 20):
162
+ # The arguments will fit to the right of the prefix.
163
+ indent = " " * term_len(usage_prefix)
164
+ self.write(
165
+ wrap_text(
166
+ args,
167
+ text_width,
168
+ initial_indent=usage_prefix,
169
+ subsequent_indent=indent,
170
+ )
171
+ )
172
+ else:
173
+ # The prefix is too long, put the arguments on the next line.
174
+ self.write(usage_prefix)
175
+ self.write("\n")
176
+ indent = " " * (max(self.current_indent, term_len(prefix)) + 4)
177
+ self.write(
178
+ wrap_text(
179
+ args, text_width, initial_indent=indent, subsequent_indent=indent
180
+ )
181
+ )
182
+
183
+ self.write("\n")
184
+
185
+ def write_heading(self, heading: str) -> None:
186
+ """Writes a heading into the buffer."""
187
+ self.write(f"{'':>{self.current_indent}}{heading}:\n")
188
+
189
+ def write_paragraph(self) -> None:
190
+ """Writes a paragraph into the buffer."""
191
+ if self.buffer:
192
+ self.write("\n")
193
+
194
+ def write_text(self, text: str) -> None:
195
+ """Writes re-indented text into the buffer. This rewraps and
196
+ preserves paragraphs.
197
+ """
198
+ indent = " " * self.current_indent
199
+ self.write(
200
+ wrap_text(
201
+ text,
202
+ self.width,
203
+ initial_indent=indent,
204
+ subsequent_indent=indent,
205
+ preserve_paragraphs=True,
206
+ )
207
+ )
208
+ self.write("\n")
209
+
210
+ def write_dl(
211
+ self,
212
+ rows: cabc.Sequence[tuple[str, str]],
213
+ col_max: int = 30,
214
+ col_spacing: int = 2,
215
+ ) -> None:
216
+ """Writes a definition list into the buffer. This is how options
217
+ and commands are usually formatted.
218
+
219
+ :param rows: a list of two item tuples for the terms and values.
220
+ :param col_max: the maximum width of the first column.
221
+ :param col_spacing: the number of spaces between the first and
222
+ second column.
223
+ """
224
+ rows = list(rows)
225
+ widths = measure_table(rows)
226
+ if len(widths) != 2:
227
+ raise TypeError("Expected two columns for definition list")
228
+
229
+ first_col = min(widths[0], col_max) + col_spacing
230
+
231
+ for first, second in iter_rows(rows, len(widths)):
232
+ self.write(f"{'':>{self.current_indent}}{first}")
233
+ if not second:
234
+ self.write("\n")
235
+ continue
236
+ if term_len(first) <= first_col - col_spacing:
237
+ self.write(" " * (first_col - term_len(first)))
238
+ else:
239
+ self.write("\n")
240
+ self.write(" " * (first_col + self.current_indent))
241
+
242
+ text_width = max(self.width - first_col - 2, 10)
243
+ wrapped_text = wrap_text(second, text_width, preserve_paragraphs=True)
244
+ lines = wrapped_text.splitlines()
245
+
246
+ if lines:
247
+ self.write(f"{lines[0]}\n")
248
+
249
+ for line in lines[1:]:
250
+ self.write(f"{'':>{first_col + self.current_indent}}{line}\n")
251
+ else:
252
+ self.write("\n")
253
+
254
+ @contextmanager
255
+ def section(self, name: str) -> cabc.Iterator[None]:
256
+ """Helpful context manager that writes a paragraph, a heading,
257
+ and the indents.
258
+
259
+ :param name: the section name that is written as heading.
260
+ """
261
+ self.write_paragraph()
262
+ self.write_heading(name)
263
+ self.indent()
264
+ try:
265
+ yield
266
+ finally:
267
+ self.dedent()
268
+
269
+ @contextmanager
270
+ def indentation(self) -> cabc.Iterator[None]:
271
+ """A context manager that increases the indentation."""
272
+ self.indent()
273
+ try:
274
+ yield
275
+ finally:
276
+ self.dedent()
277
+
278
+ def getvalue(self) -> str:
279
+ """Returns the buffer contents."""
280
+ return "".join(self.buffer)
281
+
282
+
283
+ def join_options(options: cabc.Sequence[str]) -> tuple[str, bool]:
284
+ """Given a list of option strings this joins them in the most appropriate
285
+ way and returns them in the form ``(formatted_string,
286
+ any_prefix_is_slash)`` where the second item in the tuple is a flag that
287
+ indicates if any of the option prefixes was a slash.
288
+ """
289
+ rv = []
290
+ any_prefix_is_slash = False
291
+
292
+ for opt in options:
293
+ prefix = _split_opt(opt)[0]
294
+
295
+ if prefix == "/":
296
+ any_prefix_is_slash = True
297
+
298
+ rv.append((len(prefix), opt))
299
+
300
+ rv.sort(key=lambda x: x[0])
301
+ return ", ".join(x[1] for x in rv), any_prefix_is_slash
lib/python3.12/site-packages/click/globals.py ADDED
@@ -0,0 +1,67 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import typing as t
4
+ from threading import local
5
+
6
+ if t.TYPE_CHECKING:
7
+ from .core import Context
8
+
9
+ _local = local()
10
+
11
+
12
+ @t.overload
13
+ def get_current_context(silent: t.Literal[False] = False) -> Context: ...
14
+
15
+
16
+ @t.overload
17
+ def get_current_context(silent: bool = ...) -> Context | None: ...
18
+
19
+
20
+ def get_current_context(silent: bool = False) -> Context | None:
21
+ """Returns the current click context. This can be used as a way to
22
+ access the current context object from anywhere. This is a more implicit
23
+ alternative to the :func:`pass_context` decorator. This function is
24
+ primarily useful for helpers such as :func:`echo` which might be
25
+ interested in changing its behavior based on the current context.
26
+
27
+ To push the current context, :meth:`Context.scope` can be used.
28
+
29
+ .. versionadded:: 5.0
30
+
31
+ :param silent: if set to `True` the return value is `None` if no context
32
+ is available. The default behavior is to raise a
33
+ :exc:`RuntimeError`.
34
+ """
35
+ try:
36
+ return t.cast("Context", _local.stack[-1])
37
+ except (AttributeError, IndexError) as e:
38
+ if not silent:
39
+ raise RuntimeError("There is no active click context.") from e
40
+
41
+ return None
42
+
43
+
44
+ def push_context(ctx: Context) -> None:
45
+ """Pushes a new context to the current stack."""
46
+ _local.__dict__.setdefault("stack", []).append(ctx)
47
+
48
+
49
+ def pop_context() -> None:
50
+ """Removes the top level from the stack."""
51
+ _local.stack.pop()
52
+
53
+
54
+ def resolve_color_default(color: bool | None = None) -> bool | None:
55
+ """Internal helper to get the default value of the color flag. If a
56
+ value is passed it's returned unchanged, otherwise it's looked up from
57
+ the current context.
58
+ """
59
+ if color is not None:
60
+ return color
61
+
62
+ ctx = get_current_context(silent=True)
63
+
64
+ if ctx is not None:
65
+ return ctx.color
66
+
67
+ return None
lib/python3.12/site-packages/click/parser.py ADDED
@@ -0,0 +1,532 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ This module started out as largely a copy paste from the stdlib's
3
+ optparse module with the features removed that we do not need from
4
+ optparse because we implement them in Click on a higher level (for
5
+ instance type handling, help formatting and a lot more).
6
+
7
+ The plan is to remove more and more from here over time.
8
+
9
+ The reason this is a different module and not optparse from the stdlib
10
+ is that there are differences in 2.x and 3.x about the error messages
11
+ generated and optparse in the stdlib uses gettext for no good reason
12
+ and might cause us issues.
13
+
14
+ Click uses parts of optparse written by Gregory P. Ward and maintained
15
+ by the Python Software Foundation. This is limited to code in parser.py.
16
+
17
+ Copyright 2001-2006 Gregory P. Ward. All rights reserved.
18
+ Copyright 2002-2006 Python Software Foundation. All rights reserved.
19
+ """
20
+
21
+ # This code uses parts of optparse written by Gregory P. Ward and
22
+ # maintained by the Python Software Foundation.
23
+ # Copyright 2001-2006 Gregory P. Ward
24
+ # Copyright 2002-2006 Python Software Foundation
25
+ from __future__ import annotations
26
+
27
+ import collections.abc as cabc
28
+ import typing as t
29
+ from collections import deque
30
+ from gettext import gettext as _
31
+ from gettext import ngettext
32
+
33
+ from .exceptions import BadArgumentUsage
34
+ from .exceptions import BadOptionUsage
35
+ from .exceptions import NoSuchOption
36
+ from .exceptions import UsageError
37
+
38
+ if t.TYPE_CHECKING:
39
+ from .core import Argument as CoreArgument
40
+ from .core import Context
41
+ from .core import Option as CoreOption
42
+ from .core import Parameter as CoreParameter
43
+
44
+ V = t.TypeVar("V")
45
+
46
+ # Sentinel value that indicates an option was passed as a flag without a
47
+ # value but is not a flag option. Option.consume_value uses this to
48
+ # prompt or use the flag_value.
49
+ _flag_needs_value = object()
50
+
51
+
52
+ def _unpack_args(
53
+ args: cabc.Sequence[str], nargs_spec: cabc.Sequence[int]
54
+ ) -> tuple[cabc.Sequence[str | cabc.Sequence[str | None] | None], list[str]]:
55
+ """Given an iterable of arguments and an iterable of nargs specifications,
56
+ it returns a tuple with all the unpacked arguments at the first index
57
+ and all remaining arguments as the second.
58
+
59
+ The nargs specification is the number of arguments that should be consumed
60
+ or `-1` to indicate that this position should eat up all the remainders.
61
+
62
+ Missing items are filled with `None`.
63
+ """
64
+ args = deque(args)
65
+ nargs_spec = deque(nargs_spec)
66
+ rv: list[str | tuple[str | None, ...] | None] = []
67
+ spos: int | None = None
68
+
69
+ def _fetch(c: deque[V]) -> V | None:
70
+ try:
71
+ if spos is None:
72
+ return c.popleft()
73
+ else:
74
+ return c.pop()
75
+ except IndexError:
76
+ return None
77
+
78
+ while nargs_spec:
79
+ nargs = _fetch(nargs_spec)
80
+
81
+ if nargs is None:
82
+ continue
83
+
84
+ if nargs == 1:
85
+ rv.append(_fetch(args))
86
+ elif nargs > 1:
87
+ x = [_fetch(args) for _ in range(nargs)]
88
+
89
+ # If we're reversed, we're pulling in the arguments in reverse,
90
+ # so we need to turn them around.
91
+ if spos is not None:
92
+ x.reverse()
93
+
94
+ rv.append(tuple(x))
95
+ elif nargs < 0:
96
+ if spos is not None:
97
+ raise TypeError("Cannot have two nargs < 0")
98
+
99
+ spos = len(rv)
100
+ rv.append(None)
101
+
102
+ # spos is the position of the wildcard (star). If it's not `None`,
103
+ # we fill it with the remainder.
104
+ if spos is not None:
105
+ rv[spos] = tuple(args)
106
+ args = []
107
+ rv[spos + 1 :] = reversed(rv[spos + 1 :])
108
+
109
+ return tuple(rv), list(args)
110
+
111
+
112
+ def _split_opt(opt: str) -> tuple[str, str]:
113
+ first = opt[:1]
114
+ if first.isalnum():
115
+ return "", opt
116
+ if opt[1:2] == first:
117
+ return opt[:2], opt[2:]
118
+ return first, opt[1:]
119
+
120
+
121
+ def _normalize_opt(opt: str, ctx: Context | None) -> str:
122
+ if ctx is None or ctx.token_normalize_func is None:
123
+ return opt
124
+ prefix, opt = _split_opt(opt)
125
+ return f"{prefix}{ctx.token_normalize_func(opt)}"
126
+
127
+
128
+ class _Option:
129
+ def __init__(
130
+ self,
131
+ obj: CoreOption,
132
+ opts: cabc.Sequence[str],
133
+ dest: str | None,
134
+ action: str | None = None,
135
+ nargs: int = 1,
136
+ const: t.Any | None = None,
137
+ ):
138
+ self._short_opts = []
139
+ self._long_opts = []
140
+ self.prefixes: set[str] = set()
141
+
142
+ for opt in opts:
143
+ prefix, value = _split_opt(opt)
144
+ if not prefix:
145
+ raise ValueError(f"Invalid start character for option ({opt})")
146
+ self.prefixes.add(prefix[0])
147
+ if len(prefix) == 1 and len(value) == 1:
148
+ self._short_opts.append(opt)
149
+ else:
150
+ self._long_opts.append(opt)
151
+ self.prefixes.add(prefix)
152
+
153
+ if action is None:
154
+ action = "store"
155
+
156
+ self.dest = dest
157
+ self.action = action
158
+ self.nargs = nargs
159
+ self.const = const
160
+ self.obj = obj
161
+
162
+ @property
163
+ def takes_value(self) -> bool:
164
+ return self.action in ("store", "append")
165
+
166
+ def process(self, value: t.Any, state: _ParsingState) -> None:
167
+ if self.action == "store":
168
+ state.opts[self.dest] = value # type: ignore
169
+ elif self.action == "store_const":
170
+ state.opts[self.dest] = self.const # type: ignore
171
+ elif self.action == "append":
172
+ state.opts.setdefault(self.dest, []).append(value) # type: ignore
173
+ elif self.action == "append_const":
174
+ state.opts.setdefault(self.dest, []).append(self.const) # type: ignore
175
+ elif self.action == "count":
176
+ state.opts[self.dest] = state.opts.get(self.dest, 0) + 1 # type: ignore
177
+ else:
178
+ raise ValueError(f"unknown action '{self.action}'")
179
+ state.order.append(self.obj)
180
+
181
+
182
+ class _Argument:
183
+ def __init__(self, obj: CoreArgument, dest: str | None, nargs: int = 1):
184
+ self.dest = dest
185
+ self.nargs = nargs
186
+ self.obj = obj
187
+
188
+ def process(
189
+ self,
190
+ value: str | cabc.Sequence[str | None] | None,
191
+ state: _ParsingState,
192
+ ) -> None:
193
+ if self.nargs > 1:
194
+ assert value is not None
195
+ holes = sum(1 for x in value if x is None)
196
+ if holes == len(value):
197
+ value = None
198
+ elif holes != 0:
199
+ raise BadArgumentUsage(
200
+ _("Argument {name!r} takes {nargs} values.").format(
201
+ name=self.dest, nargs=self.nargs
202
+ )
203
+ )
204
+
205
+ if self.nargs == -1 and self.obj.envvar is not None and value == ():
206
+ # Replace empty tuple with None so that a value from the
207
+ # environment may be tried.
208
+ value = None
209
+
210
+ state.opts[self.dest] = value # type: ignore
211
+ state.order.append(self.obj)
212
+
213
+
214
+ class _ParsingState:
215
+ def __init__(self, rargs: list[str]) -> None:
216
+ self.opts: dict[str, t.Any] = {}
217
+ self.largs: list[str] = []
218
+ self.rargs = rargs
219
+ self.order: list[CoreParameter] = []
220
+
221
+
222
+ class _OptionParser:
223
+ """The option parser is an internal class that is ultimately used to
224
+ parse options and arguments. It's modelled after optparse and brings
225
+ a similar but vastly simplified API. It should generally not be used
226
+ directly as the high level Click classes wrap it for you.
227
+
228
+ It's not nearly as extensible as optparse or argparse as it does not
229
+ implement features that are implemented on a higher level (such as
230
+ types or defaults).
231
+
232
+ :param ctx: optionally the :class:`~click.Context` where this parser
233
+ should go with.
234
+
235
+ .. deprecated:: 8.2
236
+ Will be removed in Click 9.0.
237
+ """
238
+
239
+ def __init__(self, ctx: Context | None = None) -> None:
240
+ #: The :class:`~click.Context` for this parser. This might be
241
+ #: `None` for some advanced use cases.
242
+ self.ctx = ctx
243
+ #: This controls how the parser deals with interspersed arguments.
244
+ #: If this is set to `False`, the parser will stop on the first
245
+ #: non-option. Click uses this to implement nested subcommands
246
+ #: safely.
247
+ self.allow_interspersed_args: bool = True
248
+ #: This tells the parser how to deal with unknown options. By
249
+ #: default it will error out (which is sensible), but there is a
250
+ #: second mode where it will ignore it and continue processing
251
+ #: after shifting all the unknown options into the resulting args.
252
+ self.ignore_unknown_options: bool = False
253
+
254
+ if ctx is not None:
255
+ self.allow_interspersed_args = ctx.allow_interspersed_args
256
+ self.ignore_unknown_options = ctx.ignore_unknown_options
257
+
258
+ self._short_opt: dict[str, _Option] = {}
259
+ self._long_opt: dict[str, _Option] = {}
260
+ self._opt_prefixes = {"-", "--"}
261
+ self._args: list[_Argument] = []
262
+
263
+ def add_option(
264
+ self,
265
+ obj: CoreOption,
266
+ opts: cabc.Sequence[str],
267
+ dest: str | None,
268
+ action: str | None = None,
269
+ nargs: int = 1,
270
+ const: t.Any | None = None,
271
+ ) -> None:
272
+ """Adds a new option named `dest` to the parser. The destination
273
+ is not inferred (unlike with optparse) and needs to be explicitly
274
+ provided. Action can be any of ``store``, ``store_const``,
275
+ ``append``, ``append_const`` or ``count``.
276
+
277
+ The `obj` can be used to identify the option in the order list
278
+ that is returned from the parser.
279
+ """
280
+ opts = [_normalize_opt(opt, self.ctx) for opt in opts]
281
+ option = _Option(obj, opts, dest, action=action, nargs=nargs, const=const)
282
+ self._opt_prefixes.update(option.prefixes)
283
+ for opt in option._short_opts:
284
+ self._short_opt[opt] = option
285
+ for opt in option._long_opts:
286
+ self._long_opt[opt] = option
287
+
288
+ def add_argument(self, obj: CoreArgument, dest: str | None, nargs: int = 1) -> None:
289
+ """Adds a positional argument named `dest` to the parser.
290
+
291
+ The `obj` can be used to identify the option in the order list
292
+ that is returned from the parser.
293
+ """
294
+ self._args.append(_Argument(obj, dest=dest, nargs=nargs))
295
+
296
+ def parse_args(
297
+ self, args: list[str]
298
+ ) -> tuple[dict[str, t.Any], list[str], list[CoreParameter]]:
299
+ """Parses positional arguments and returns ``(values, args, order)``
300
+ for the parsed options and arguments as well as the leftover
301
+ arguments if there are any. The order is a list of objects as they
302
+ appear on the command line. If arguments appear multiple times they
303
+ will be memorized multiple times as well.
304
+ """
305
+ state = _ParsingState(args)
306
+ try:
307
+ self._process_args_for_options(state)
308
+ self._process_args_for_args(state)
309
+ except UsageError:
310
+ if self.ctx is None or not self.ctx.resilient_parsing:
311
+ raise
312
+ return state.opts, state.largs, state.order
313
+
314
+ def _process_args_for_args(self, state: _ParsingState) -> None:
315
+ pargs, args = _unpack_args(
316
+ state.largs + state.rargs, [x.nargs for x in self._args]
317
+ )
318
+
319
+ for idx, arg in enumerate(self._args):
320
+ arg.process(pargs[idx], state)
321
+
322
+ state.largs = args
323
+ state.rargs = []
324
+
325
+ def _process_args_for_options(self, state: _ParsingState) -> None:
326
+ while state.rargs:
327
+ arg = state.rargs.pop(0)
328
+ arglen = len(arg)
329
+ # Double dashes always handled explicitly regardless of what
330
+ # prefixes are valid.
331
+ if arg == "--":
332
+ return
333
+ elif arg[:1] in self._opt_prefixes and arglen > 1:
334
+ self._process_opts(arg, state)
335
+ elif self.allow_interspersed_args:
336
+ state.largs.append(arg)
337
+ else:
338
+ state.rargs.insert(0, arg)
339
+ return
340
+
341
+ # Say this is the original argument list:
342
+ # [arg0, arg1, ..., arg(i-1), arg(i), arg(i+1), ..., arg(N-1)]
343
+ # ^
344
+ # (we are about to process arg(i)).
345
+ #
346
+ # Then rargs is [arg(i), ..., arg(N-1)] and largs is a *subset* of
347
+ # [arg0, ..., arg(i-1)] (any options and their arguments will have
348
+ # been removed from largs).
349
+ #
350
+ # The while loop will usually consume 1 or more arguments per pass.
351
+ # If it consumes 1 (eg. arg is an option that takes no arguments),
352
+ # then after _process_arg() is done the situation is:
353
+ #
354
+ # largs = subset of [arg0, ..., arg(i)]
355
+ # rargs = [arg(i+1), ..., arg(N-1)]
356
+ #
357
+ # If allow_interspersed_args is false, largs will always be
358
+ # *empty* -- still a subset of [arg0, ..., arg(i-1)], but
359
+ # not a very interesting subset!
360
+
361
+ def _match_long_opt(
362
+ self, opt: str, explicit_value: str | None, state: _ParsingState
363
+ ) -> None:
364
+ if opt not in self._long_opt:
365
+ from difflib import get_close_matches
366
+
367
+ possibilities = get_close_matches(opt, self._long_opt)
368
+ raise NoSuchOption(opt, possibilities=possibilities, ctx=self.ctx)
369
+
370
+ option = self._long_opt[opt]
371
+ if option.takes_value:
372
+ # At this point it's safe to modify rargs by injecting the
373
+ # explicit value, because no exception is raised in this
374
+ # branch. This means that the inserted value will be fully
375
+ # consumed.
376
+ if explicit_value is not None:
377
+ state.rargs.insert(0, explicit_value)
378
+
379
+ value = self._get_value_from_state(opt, option, state)
380
+
381
+ elif explicit_value is not None:
382
+ raise BadOptionUsage(
383
+ opt, _("Option {name!r} does not take a value.").format(name=opt)
384
+ )
385
+
386
+ else:
387
+ value = None
388
+
389
+ option.process(value, state)
390
+
391
+ def _match_short_opt(self, arg: str, state: _ParsingState) -> None:
392
+ stop = False
393
+ i = 1
394
+ prefix = arg[0]
395
+ unknown_options = []
396
+
397
+ for ch in arg[1:]:
398
+ opt = _normalize_opt(f"{prefix}{ch}", self.ctx)
399
+ option = self._short_opt.get(opt)
400
+ i += 1
401
+
402
+ if not option:
403
+ if self.ignore_unknown_options:
404
+ unknown_options.append(ch)
405
+ continue
406
+ raise NoSuchOption(opt, ctx=self.ctx)
407
+ if option.takes_value:
408
+ # Any characters left in arg? Pretend they're the
409
+ # next arg, and stop consuming characters of arg.
410
+ if i < len(arg):
411
+ state.rargs.insert(0, arg[i:])
412
+ stop = True
413
+
414
+ value = self._get_value_from_state(opt, option, state)
415
+
416
+ else:
417
+ value = None
418
+
419
+ option.process(value, state)
420
+
421
+ if stop:
422
+ break
423
+
424
+ # If we got any unknown options we recombine the string of the
425
+ # remaining options and re-attach the prefix, then report that
426
+ # to the state as new larg. This way there is basic combinatorics
427
+ # that can be achieved while still ignoring unknown arguments.
428
+ if self.ignore_unknown_options and unknown_options:
429
+ state.largs.append(f"{prefix}{''.join(unknown_options)}")
430
+
431
+ def _get_value_from_state(
432
+ self, option_name: str, option: _Option, state: _ParsingState
433
+ ) -> t.Any:
434
+ nargs = option.nargs
435
+
436
+ if len(state.rargs) < nargs:
437
+ if option.obj._flag_needs_value:
438
+ # Option allows omitting the value.
439
+ value = _flag_needs_value
440
+ else:
441
+ raise BadOptionUsage(
442
+ option_name,
443
+ ngettext(
444
+ "Option {name!r} requires an argument.",
445
+ "Option {name!r} requires {nargs} arguments.",
446
+ nargs,
447
+ ).format(name=option_name, nargs=nargs),
448
+ )
449
+ elif nargs == 1:
450
+ next_rarg = state.rargs[0]
451
+
452
+ if (
453
+ option.obj._flag_needs_value
454
+ and isinstance(next_rarg, str)
455
+ and next_rarg[:1] in self._opt_prefixes
456
+ and len(next_rarg) > 1
457
+ ):
458
+ # The next arg looks like the start of an option, don't
459
+ # use it as the value if omitting the value is allowed.
460
+ value = _flag_needs_value
461
+ else:
462
+ value = state.rargs.pop(0)
463
+ else:
464
+ value = tuple(state.rargs[:nargs])
465
+ del state.rargs[:nargs]
466
+
467
+ return value
468
+
469
+ def _process_opts(self, arg: str, state: _ParsingState) -> None:
470
+ explicit_value = None
471
+ # Long option handling happens in two parts. The first part is
472
+ # supporting explicitly attached values. In any case, we will try
473
+ # to long match the option first.
474
+ if "=" in arg:
475
+ long_opt, explicit_value = arg.split("=", 1)
476
+ else:
477
+ long_opt = arg
478
+ norm_long_opt = _normalize_opt(long_opt, self.ctx)
479
+
480
+ # At this point we will match the (assumed) long option through
481
+ # the long option matching code. Note that this allows options
482
+ # like "-foo" to be matched as long options.
483
+ try:
484
+ self._match_long_opt(norm_long_opt, explicit_value, state)
485
+ except NoSuchOption:
486
+ # At this point the long option matching failed, and we need
487
+ # to try with short options. However there is a special rule
488
+ # which says, that if we have a two character options prefix
489
+ # (applies to "--foo" for instance), we do not dispatch to the
490
+ # short option code and will instead raise the no option
491
+ # error.
492
+ if arg[:2] not in self._opt_prefixes:
493
+ self._match_short_opt(arg, state)
494
+ return
495
+
496
+ if not self.ignore_unknown_options:
497
+ raise
498
+
499
+ state.largs.append(arg)
500
+
501
+
502
+ def __getattr__(name: str) -> object:
503
+ import warnings
504
+
505
+ if name in {
506
+ "OptionParser",
507
+ "Argument",
508
+ "Option",
509
+ "split_opt",
510
+ "normalize_opt",
511
+ "ParsingState",
512
+ }:
513
+ warnings.warn(
514
+ f"'parser.{name}' is deprecated and will be removed in Click 9.0."
515
+ " The old parser is available in 'optparse'.",
516
+ DeprecationWarning,
517
+ stacklevel=2,
518
+ )
519
+ return globals()[f"_{name}"]
520
+
521
+ if name == "split_arg_string":
522
+ from .shell_completion import split_arg_string
523
+
524
+ warnings.warn(
525
+ "Importing 'parser.split_arg_string' is deprecated, it will only be"
526
+ " available in 'shell_completion' in Click 9.0.",
527
+ DeprecationWarning,
528
+ stacklevel=2,
529
+ )
530
+ return split_arg_string
531
+
532
+ raise AttributeError(name)
lib/python3.12/site-packages/click/py.typed ADDED
File without changes
lib/python3.12/site-packages/click/shell_completion.py ADDED
@@ -0,0 +1,644 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import collections.abc as cabc
4
+ import os
5
+ import re
6
+ import typing as t
7
+ from gettext import gettext as _
8
+
9
+ from .core import Argument
10
+ from .core import Command
11
+ from .core import Context
12
+ from .core import Group
13
+ from .core import Option
14
+ from .core import Parameter
15
+ from .core import ParameterSource
16
+ from .utils import echo
17
+
18
+
19
+ def shell_complete(
20
+ cli: Command,
21
+ ctx_args: cabc.MutableMapping[str, t.Any],
22
+ prog_name: str,
23
+ complete_var: str,
24
+ instruction: str,
25
+ ) -> int:
26
+ """Perform shell completion for the given CLI program.
27
+
28
+ :param cli: Command being called.
29
+ :param ctx_args: Extra arguments to pass to
30
+ ``cli.make_context``.
31
+ :param prog_name: Name of the executable in the shell.
32
+ :param complete_var: Name of the environment variable that holds
33
+ the completion instruction.
34
+ :param instruction: Value of ``complete_var`` with the completion
35
+ instruction and shell, in the form ``instruction_shell``.
36
+ :return: Status code to exit with.
37
+ """
38
+ shell, _, instruction = instruction.partition("_")
39
+ comp_cls = get_completion_class(shell)
40
+
41
+ if comp_cls is None:
42
+ return 1
43
+
44
+ comp = comp_cls(cli, ctx_args, prog_name, complete_var)
45
+
46
+ if instruction == "source":
47
+ echo(comp.source())
48
+ return 0
49
+
50
+ if instruction == "complete":
51
+ echo(comp.complete())
52
+ return 0
53
+
54
+ return 1
55
+
56
+
57
+ class CompletionItem:
58
+ """Represents a completion value and metadata about the value. The
59
+ default metadata is ``type`` to indicate special shell handling,
60
+ and ``help`` if a shell supports showing a help string next to the
61
+ value.
62
+
63
+ Arbitrary parameters can be passed when creating the object, and
64
+ accessed using ``item.attr``. If an attribute wasn't passed,
65
+ accessing it returns ``None``.
66
+
67
+ :param value: The completion suggestion.
68
+ :param type: Tells the shell script to provide special completion
69
+ support for the type. Click uses ``"dir"`` and ``"file"``.
70
+ :param help: String shown next to the value if supported.
71
+ :param kwargs: Arbitrary metadata. The built-in implementations
72
+ don't use this, but custom type completions paired with custom
73
+ shell support could use it.
74
+ """
75
+
76
+ __slots__ = ("value", "type", "help", "_info")
77
+
78
+ def __init__(
79
+ self,
80
+ value: t.Any,
81
+ type: str = "plain",
82
+ help: str | None = None,
83
+ **kwargs: t.Any,
84
+ ) -> None:
85
+ self.value: t.Any = value
86
+ self.type: str = type
87
+ self.help: str | None = help
88
+ self._info = kwargs
89
+
90
+ def __getattr__(self, name: str) -> t.Any:
91
+ return self._info.get(name)
92
+
93
+
94
+ # Only Bash >= 4.4 has the nosort option.
95
+ _SOURCE_BASH = """\
96
+ %(complete_func)s() {
97
+ local IFS=$'\\n'
98
+ local response
99
+
100
+ response=$(env COMP_WORDS="${COMP_WORDS[*]}" COMP_CWORD=$COMP_CWORD \
101
+ %(complete_var)s=bash_complete $1)
102
+
103
+ for completion in $response; do
104
+ IFS=',' read type value <<< "$completion"
105
+
106
+ if [[ $type == 'dir' ]]; then
107
+ COMPREPLY=()
108
+ compopt -o dirnames
109
+ elif [[ $type == 'file' ]]; then
110
+ COMPREPLY=()
111
+ compopt -o default
112
+ elif [[ $type == 'plain' ]]; then
113
+ COMPREPLY+=($value)
114
+ fi
115
+ done
116
+
117
+ return 0
118
+ }
119
+
120
+ %(complete_func)s_setup() {
121
+ complete -o nosort -F %(complete_func)s %(prog_name)s
122
+ }
123
+
124
+ %(complete_func)s_setup;
125
+ """
126
+
127
+ _SOURCE_ZSH = """\
128
+ #compdef %(prog_name)s
129
+
130
+ %(complete_func)s() {
131
+ local -a completions
132
+ local -a completions_with_descriptions
133
+ local -a response
134
+ (( ! $+commands[%(prog_name)s] )) && return 1
135
+
136
+ response=("${(@f)$(env COMP_WORDS="${words[*]}" COMP_CWORD=$((CURRENT-1)) \
137
+ %(complete_var)s=zsh_complete %(prog_name)s)}")
138
+
139
+ for type key descr in ${response}; do
140
+ if [[ "$type" == "plain" ]]; then
141
+ if [[ "$descr" == "_" ]]; then
142
+ completions+=("$key")
143
+ else
144
+ completions_with_descriptions+=("$key":"$descr")
145
+ fi
146
+ elif [[ "$type" == "dir" ]]; then
147
+ _path_files -/
148
+ elif [[ "$type" == "file" ]]; then
149
+ _path_files -f
150
+ fi
151
+ done
152
+
153
+ if [ -n "$completions_with_descriptions" ]; then
154
+ _describe -V unsorted completions_with_descriptions -U
155
+ fi
156
+
157
+ if [ -n "$completions" ]; then
158
+ compadd -U -V unsorted -a completions
159
+ fi
160
+ }
161
+
162
+ if [[ $zsh_eval_context[-1] == loadautofunc ]]; then
163
+ # autoload from fpath, call function directly
164
+ %(complete_func)s "$@"
165
+ else
166
+ # eval/source/. command, register function for later
167
+ compdef %(complete_func)s %(prog_name)s
168
+ fi
169
+ """
170
+
171
+ _SOURCE_FISH = """\
172
+ function %(complete_func)s;
173
+ set -l response (env %(complete_var)s=fish_complete COMP_WORDS=(commandline -cp) \
174
+ COMP_CWORD=(commandline -t) %(prog_name)s);
175
+
176
+ for completion in $response;
177
+ set -l metadata (string split "," $completion);
178
+
179
+ if test $metadata[1] = "dir";
180
+ __fish_complete_directories $metadata[2];
181
+ else if test $metadata[1] = "file";
182
+ __fish_complete_path $metadata[2];
183
+ else if test $metadata[1] = "plain";
184
+ echo $metadata[2];
185
+ end;
186
+ end;
187
+ end;
188
+
189
+ complete --no-files --command %(prog_name)s --arguments \
190
+ "(%(complete_func)s)";
191
+ """
192
+
193
+
194
+ class ShellComplete:
195
+ """Base class for providing shell completion support. A subclass for
196
+ a given shell will override attributes and methods to implement the
197
+ completion instructions (``source`` and ``complete``).
198
+
199
+ :param cli: Command being called.
200
+ :param prog_name: Name of the executable in the shell.
201
+ :param complete_var: Name of the environment variable that holds
202
+ the completion instruction.
203
+
204
+ .. versionadded:: 8.0
205
+ """
206
+
207
+ name: t.ClassVar[str]
208
+ """Name to register the shell as with :func:`add_completion_class`.
209
+ This is used in completion instructions (``{name}_source`` and
210
+ ``{name}_complete``).
211
+ """
212
+
213
+ source_template: t.ClassVar[str]
214
+ """Completion script template formatted by :meth:`source`. This must
215
+ be provided by subclasses.
216
+ """
217
+
218
+ def __init__(
219
+ self,
220
+ cli: Command,
221
+ ctx_args: cabc.MutableMapping[str, t.Any],
222
+ prog_name: str,
223
+ complete_var: str,
224
+ ) -> None:
225
+ self.cli = cli
226
+ self.ctx_args = ctx_args
227
+ self.prog_name = prog_name
228
+ self.complete_var = complete_var
229
+
230
+ @property
231
+ def func_name(self) -> str:
232
+ """The name of the shell function defined by the completion
233
+ script.
234
+ """
235
+ safe_name = re.sub(r"\W*", "", self.prog_name.replace("-", "_"), flags=re.ASCII)
236
+ return f"_{safe_name}_completion"
237
+
238
+ def source_vars(self) -> dict[str, t.Any]:
239
+ """Vars for formatting :attr:`source_template`.
240
+
241
+ By default this provides ``complete_func``, ``complete_var``,
242
+ and ``prog_name``.
243
+ """
244
+ return {
245
+ "complete_func": self.func_name,
246
+ "complete_var": self.complete_var,
247
+ "prog_name": self.prog_name,
248
+ }
249
+
250
+ def source(self) -> str:
251
+ """Produce the shell script that defines the completion
252
+ function. By default this ``%``-style formats
253
+ :attr:`source_template` with the dict returned by
254
+ :meth:`source_vars`.
255
+ """
256
+ return self.source_template % self.source_vars()
257
+
258
+ def get_completion_args(self) -> tuple[list[str], str]:
259
+ """Use the env vars defined by the shell script to return a
260
+ tuple of ``args, incomplete``. This must be implemented by
261
+ subclasses.
262
+ """
263
+ raise NotImplementedError
264
+
265
+ def get_completions(self, args: list[str], incomplete: str) -> list[CompletionItem]:
266
+ """Determine the context and last complete command or parameter
267
+ from the complete args. Call that object's ``shell_complete``
268
+ method to get the completions for the incomplete value.
269
+
270
+ :param args: List of complete args before the incomplete value.
271
+ :param incomplete: Value being completed. May be empty.
272
+ """
273
+ ctx = _resolve_context(self.cli, self.ctx_args, self.prog_name, args)
274
+ obj, incomplete = _resolve_incomplete(ctx, args, incomplete)
275
+ return obj.shell_complete(ctx, incomplete)
276
+
277
+ def format_completion(self, item: CompletionItem) -> str:
278
+ """Format a completion item into the form recognized by the
279
+ shell script. This must be implemented by subclasses.
280
+
281
+ :param item: Completion item to format.
282
+ """
283
+ raise NotImplementedError
284
+
285
+ def complete(self) -> str:
286
+ """Produce the completion data to send back to the shell.
287
+
288
+ By default this calls :meth:`get_completion_args`, gets the
289
+ completions, then calls :meth:`format_completion` for each
290
+ completion.
291
+ """
292
+ args, incomplete = self.get_completion_args()
293
+ completions = self.get_completions(args, incomplete)
294
+ out = [self.format_completion(item) for item in completions]
295
+ return "\n".join(out)
296
+
297
+
298
+ class BashComplete(ShellComplete):
299
+ """Shell completion for Bash."""
300
+
301
+ name = "bash"
302
+ source_template = _SOURCE_BASH
303
+
304
+ @staticmethod
305
+ def _check_version() -> None:
306
+ import shutil
307
+ import subprocess
308
+
309
+ bash_exe = shutil.which("bash")
310
+
311
+ if bash_exe is None:
312
+ match = None
313
+ else:
314
+ output = subprocess.run(
315
+ [bash_exe, "--norc", "-c", 'echo "${BASH_VERSION}"'],
316
+ stdout=subprocess.PIPE,
317
+ )
318
+ match = re.search(r"^(\d+)\.(\d+)\.\d+", output.stdout.decode())
319
+
320
+ if match is not None:
321
+ major, minor = match.groups()
322
+
323
+ if major < "4" or major == "4" and minor < "4":
324
+ echo(
325
+ _(
326
+ "Shell completion is not supported for Bash"
327
+ " versions older than 4.4."
328
+ ),
329
+ err=True,
330
+ )
331
+ else:
332
+ echo(
333
+ _("Couldn't detect Bash version, shell completion is not supported."),
334
+ err=True,
335
+ )
336
+
337
+ def source(self) -> str:
338
+ self._check_version()
339
+ return super().source()
340
+
341
+ def get_completion_args(self) -> tuple[list[str], str]:
342
+ cwords = split_arg_string(os.environ["COMP_WORDS"])
343
+ cword = int(os.environ["COMP_CWORD"])
344
+ args = cwords[1:cword]
345
+
346
+ try:
347
+ incomplete = cwords[cword]
348
+ except IndexError:
349
+ incomplete = ""
350
+
351
+ return args, incomplete
352
+
353
+ def format_completion(self, item: CompletionItem) -> str:
354
+ return f"{item.type},{item.value}"
355
+
356
+
357
+ class ZshComplete(ShellComplete):
358
+ """Shell completion for Zsh."""
359
+
360
+ name = "zsh"
361
+ source_template = _SOURCE_ZSH
362
+
363
+ def get_completion_args(self) -> tuple[list[str], str]:
364
+ cwords = split_arg_string(os.environ["COMP_WORDS"])
365
+ cword = int(os.environ["COMP_CWORD"])
366
+ args = cwords[1:cword]
367
+
368
+ try:
369
+ incomplete = cwords[cword]
370
+ except IndexError:
371
+ incomplete = ""
372
+
373
+ return args, incomplete
374
+
375
+ def format_completion(self, item: CompletionItem) -> str:
376
+ return f"{item.type}\n{item.value}\n{item.help if item.help else '_'}"
377
+
378
+
379
+ class FishComplete(ShellComplete):
380
+ """Shell completion for Fish."""
381
+
382
+ name = "fish"
383
+ source_template = _SOURCE_FISH
384
+
385
+ def get_completion_args(self) -> tuple[list[str], str]:
386
+ cwords = split_arg_string(os.environ["COMP_WORDS"])
387
+ incomplete = os.environ["COMP_CWORD"]
388
+ args = cwords[1:]
389
+
390
+ # Fish stores the partial word in both COMP_WORDS and
391
+ # COMP_CWORD, remove it from complete args.
392
+ if incomplete and args and args[-1] == incomplete:
393
+ args.pop()
394
+
395
+ return args, incomplete
396
+
397
+ def format_completion(self, item: CompletionItem) -> str:
398
+ if item.help:
399
+ return f"{item.type},{item.value}\t{item.help}"
400
+
401
+ return f"{item.type},{item.value}"
402
+
403
+
404
+ ShellCompleteType = t.TypeVar("ShellCompleteType", bound="type[ShellComplete]")
405
+
406
+
407
+ _available_shells: dict[str, type[ShellComplete]] = {
408
+ "bash": BashComplete,
409
+ "fish": FishComplete,
410
+ "zsh": ZshComplete,
411
+ }
412
+
413
+
414
+ def add_completion_class(
415
+ cls: ShellCompleteType, name: str | None = None
416
+ ) -> ShellCompleteType:
417
+ """Register a :class:`ShellComplete` subclass under the given name.
418
+ The name will be provided by the completion instruction environment
419
+ variable during completion.
420
+
421
+ :param cls: The completion class that will handle completion for the
422
+ shell.
423
+ :param name: Name to register the class under. Defaults to the
424
+ class's ``name`` attribute.
425
+ """
426
+ if name is None:
427
+ name = cls.name
428
+
429
+ _available_shells[name] = cls
430
+
431
+ return cls
432
+
433
+
434
+ def get_completion_class(shell: str) -> type[ShellComplete] | None:
435
+ """Look up a registered :class:`ShellComplete` subclass by the name
436
+ provided by the completion instruction environment variable. If the
437
+ name isn't registered, returns ``None``.
438
+
439
+ :param shell: Name the class is registered under.
440
+ """
441
+ return _available_shells.get(shell)
442
+
443
+
444
+ def split_arg_string(string: str) -> list[str]:
445
+ """Split an argument string as with :func:`shlex.split`, but don't
446
+ fail if the string is incomplete. Ignores a missing closing quote or
447
+ incomplete escape sequence and uses the partial token as-is.
448
+
449
+ .. code-block:: python
450
+
451
+ split_arg_string("example 'my file")
452
+ ["example", "my file"]
453
+
454
+ split_arg_string("example my\\")
455
+ ["example", "my"]
456
+
457
+ :param string: String to split.
458
+
459
+ .. versionchanged:: 8.2
460
+ Moved to ``shell_completion`` from ``parser``.
461
+ """
462
+ import shlex
463
+
464
+ lex = shlex.shlex(string, posix=True)
465
+ lex.whitespace_split = True
466
+ lex.commenters = ""
467
+ out = []
468
+
469
+ try:
470
+ for token in lex:
471
+ out.append(token)
472
+ except ValueError:
473
+ # Raised when end-of-string is reached in an invalid state. Use
474
+ # the partial token as-is. The quote or escape character is in
475
+ # lex.state, not lex.token.
476
+ out.append(lex.token)
477
+
478
+ return out
479
+
480
+
481
+ def _is_incomplete_argument(ctx: Context, param: Parameter) -> bool:
482
+ """Determine if the given parameter is an argument that can still
483
+ accept values.
484
+
485
+ :param ctx: Invocation context for the command represented by the
486
+ parsed complete args.
487
+ :param param: Argument object being checked.
488
+ """
489
+ if not isinstance(param, Argument):
490
+ return False
491
+
492
+ assert param.name is not None
493
+ # Will be None if expose_value is False.
494
+ value = ctx.params.get(param.name)
495
+ return (
496
+ param.nargs == -1
497
+ or ctx.get_parameter_source(param.name) is not ParameterSource.COMMANDLINE
498
+ or (
499
+ param.nargs > 1
500
+ and isinstance(value, (tuple, list))
501
+ and len(value) < param.nargs
502
+ )
503
+ )
504
+
505
+
506
+ def _start_of_option(ctx: Context, value: str) -> bool:
507
+ """Check if the value looks like the start of an option."""
508
+ if not value:
509
+ return False
510
+
511
+ c = value[0]
512
+ return c in ctx._opt_prefixes
513
+
514
+
515
+ def _is_incomplete_option(ctx: Context, args: list[str], param: Parameter) -> bool:
516
+ """Determine if the given parameter is an option that needs a value.
517
+
518
+ :param args: List of complete args before the incomplete value.
519
+ :param param: Option object being checked.
520
+ """
521
+ if not isinstance(param, Option):
522
+ return False
523
+
524
+ if param.is_flag or param.count:
525
+ return False
526
+
527
+ last_option = None
528
+
529
+ for index, arg in enumerate(reversed(args)):
530
+ if index + 1 > param.nargs:
531
+ break
532
+
533
+ if _start_of_option(ctx, arg):
534
+ last_option = arg
535
+
536
+ return last_option is not None and last_option in param.opts
537
+
538
+
539
+ def _resolve_context(
540
+ cli: Command,
541
+ ctx_args: cabc.MutableMapping[str, t.Any],
542
+ prog_name: str,
543
+ args: list[str],
544
+ ) -> Context:
545
+ """Produce the context hierarchy starting with the command and
546
+ traversing the complete arguments. This only follows the commands,
547
+ it doesn't trigger input prompts or callbacks.
548
+
549
+ :param cli: Command being called.
550
+ :param prog_name: Name of the executable in the shell.
551
+ :param args: List of complete args before the incomplete value.
552
+ """
553
+ ctx_args["resilient_parsing"] = True
554
+ with cli.make_context(prog_name, args.copy(), **ctx_args) as ctx:
555
+ args = ctx._protected_args + ctx.args
556
+
557
+ while args:
558
+ command = ctx.command
559
+
560
+ if isinstance(command, Group):
561
+ if not command.chain:
562
+ name, cmd, args = command.resolve_command(ctx, args)
563
+
564
+ if cmd is None:
565
+ return ctx
566
+
567
+ with cmd.make_context(
568
+ name, args, parent=ctx, resilient_parsing=True
569
+ ) as sub_ctx:
570
+ ctx = sub_ctx
571
+ args = ctx._protected_args + ctx.args
572
+ else:
573
+ sub_ctx = ctx
574
+
575
+ while args:
576
+ name, cmd, args = command.resolve_command(ctx, args)
577
+
578
+ if cmd is None:
579
+ return ctx
580
+
581
+ with cmd.make_context(
582
+ name,
583
+ args,
584
+ parent=ctx,
585
+ allow_extra_args=True,
586
+ allow_interspersed_args=False,
587
+ resilient_parsing=True,
588
+ ) as sub_sub_ctx:
589
+ sub_ctx = sub_sub_ctx
590
+ args = sub_ctx.args
591
+
592
+ ctx = sub_ctx
593
+ args = [*sub_ctx._protected_args, *sub_ctx.args]
594
+ else:
595
+ break
596
+
597
+ return ctx
598
+
599
+
600
+ def _resolve_incomplete(
601
+ ctx: Context, args: list[str], incomplete: str
602
+ ) -> tuple[Command | Parameter, str]:
603
+ """Find the Click object that will handle the completion of the
604
+ incomplete value. Return the object and the incomplete value.
605
+
606
+ :param ctx: Invocation context for the command represented by
607
+ the parsed complete args.
608
+ :param args: List of complete args before the incomplete value.
609
+ :param incomplete: Value being completed. May be empty.
610
+ """
611
+ # Different shells treat an "=" between a long option name and
612
+ # value differently. Might keep the value joined, return the "="
613
+ # as a separate item, or return the split name and value. Always
614
+ # split and discard the "=" to make completion easier.
615
+ if incomplete == "=":
616
+ incomplete = ""
617
+ elif "=" in incomplete and _start_of_option(ctx, incomplete):
618
+ name, _, incomplete = incomplete.partition("=")
619
+ args.append(name)
620
+
621
+ # The "--" marker tells Click to stop treating values as options
622
+ # even if they start with the option character. If it hasn't been
623
+ # given and the incomplete arg looks like an option, the current
624
+ # command will provide option name completions.
625
+ if "--" not in args and _start_of_option(ctx, incomplete):
626
+ return ctx.command, incomplete
627
+
628
+ params = ctx.command.get_params(ctx)
629
+
630
+ # If the last complete arg is an option name with an incomplete
631
+ # value, the option will provide value completions.
632
+ for param in params:
633
+ if _is_incomplete_option(ctx, args, param):
634
+ return param, incomplete
635
+
636
+ # It's not an option name or value. The first argument without a
637
+ # parsed value will provide value completions.
638
+ for param in params:
639
+ if _is_incomplete_argument(ctx, param):
640
+ return param, incomplete
641
+
642
+ # There were no unparsed arguments, the command may be a group that
643
+ # will provide command name completions.
644
+ return ctx.command, incomplete
lib/python3.12/site-packages/click/termui.py ADDED
@@ -0,0 +1,877 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import collections.abc as cabc
4
+ import inspect
5
+ import io
6
+ import itertools
7
+ import sys
8
+ import typing as t
9
+ from contextlib import AbstractContextManager
10
+ from gettext import gettext as _
11
+
12
+ from ._compat import isatty
13
+ from ._compat import strip_ansi
14
+ from .exceptions import Abort
15
+ from .exceptions import UsageError
16
+ from .globals import resolve_color_default
17
+ from .types import Choice
18
+ from .types import convert_type
19
+ from .types import ParamType
20
+ from .utils import echo
21
+ from .utils import LazyFile
22
+
23
+ if t.TYPE_CHECKING:
24
+ from ._termui_impl import ProgressBar
25
+
26
+ V = t.TypeVar("V")
27
+
28
+ # The prompt functions to use. The doc tools currently override these
29
+ # functions to customize how they work.
30
+ visible_prompt_func: t.Callable[[str], str] = input
31
+
32
+ _ansi_colors = {
33
+ "black": 30,
34
+ "red": 31,
35
+ "green": 32,
36
+ "yellow": 33,
37
+ "blue": 34,
38
+ "magenta": 35,
39
+ "cyan": 36,
40
+ "white": 37,
41
+ "reset": 39,
42
+ "bright_black": 90,
43
+ "bright_red": 91,
44
+ "bright_green": 92,
45
+ "bright_yellow": 93,
46
+ "bright_blue": 94,
47
+ "bright_magenta": 95,
48
+ "bright_cyan": 96,
49
+ "bright_white": 97,
50
+ }
51
+ _ansi_reset_all = "\033[0m"
52
+
53
+
54
+ def hidden_prompt_func(prompt: str) -> str:
55
+ import getpass
56
+
57
+ return getpass.getpass(prompt)
58
+
59
+
60
+ def _build_prompt(
61
+ text: str,
62
+ suffix: str,
63
+ show_default: bool = False,
64
+ default: t.Any | None = None,
65
+ show_choices: bool = True,
66
+ type: ParamType | None = None,
67
+ ) -> str:
68
+ prompt = text
69
+ if type is not None and show_choices and isinstance(type, Choice):
70
+ prompt += f" ({', '.join(map(str, type.choices))})"
71
+ if default is not None and show_default:
72
+ prompt = f"{prompt} [{_format_default(default)}]"
73
+ return f"{prompt}{suffix}"
74
+
75
+
76
+ def _format_default(default: t.Any) -> t.Any:
77
+ if isinstance(default, (io.IOBase, LazyFile)) and hasattr(default, "name"):
78
+ return default.name
79
+
80
+ return default
81
+
82
+
83
+ def prompt(
84
+ text: str,
85
+ default: t.Any | None = None,
86
+ hide_input: bool = False,
87
+ confirmation_prompt: bool | str = False,
88
+ type: ParamType | t.Any | None = None,
89
+ value_proc: t.Callable[[str], t.Any] | None = None,
90
+ prompt_suffix: str = ": ",
91
+ show_default: bool = True,
92
+ err: bool = False,
93
+ show_choices: bool = True,
94
+ ) -> t.Any:
95
+ """Prompts a user for input. This is a convenience function that can
96
+ be used to prompt a user for input later.
97
+
98
+ If the user aborts the input by sending an interrupt signal, this
99
+ function will catch it and raise a :exc:`Abort` exception.
100
+
101
+ :param text: the text to show for the prompt.
102
+ :param default: the default value to use if no input happens. If this
103
+ is not given it will prompt until it's aborted.
104
+ :param hide_input: if this is set to true then the input value will
105
+ be hidden.
106
+ :param confirmation_prompt: Prompt a second time to confirm the
107
+ value. Can be set to a string instead of ``True`` to customize
108
+ the message.
109
+ :param type: the type to use to check the value against.
110
+ :param value_proc: if this parameter is provided it's a function that
111
+ is invoked instead of the type conversion to
112
+ convert a value.
113
+ :param prompt_suffix: a suffix that should be added to the prompt.
114
+ :param show_default: shows or hides the default value in the prompt.
115
+ :param err: if set to true the file defaults to ``stderr`` instead of
116
+ ``stdout``, the same as with echo.
117
+ :param show_choices: Show or hide choices if the passed type is a Choice.
118
+ For example if type is a Choice of either day or week,
119
+ show_choices is true and text is "Group by" then the
120
+ prompt will be "Group by (day, week): ".
121
+
122
+ .. versionadded:: 8.0
123
+ ``confirmation_prompt`` can be a custom string.
124
+
125
+ .. versionadded:: 7.0
126
+ Added the ``show_choices`` parameter.
127
+
128
+ .. versionadded:: 6.0
129
+ Added unicode support for cmd.exe on Windows.
130
+
131
+ .. versionadded:: 4.0
132
+ Added the `err` parameter.
133
+
134
+ """
135
+
136
+ def prompt_func(text: str) -> str:
137
+ f = hidden_prompt_func if hide_input else visible_prompt_func
138
+ try:
139
+ # Write the prompt separately so that we get nice
140
+ # coloring through colorama on Windows
141
+ echo(text.rstrip(" "), nl=False, err=err)
142
+ # Echo a space to stdout to work around an issue where
143
+ # readline causes backspace to clear the whole line.
144
+ return f(" ")
145
+ except (KeyboardInterrupt, EOFError):
146
+ # getpass doesn't print a newline if the user aborts input with ^C.
147
+ # Allegedly this behavior is inherited from getpass(3).
148
+ # A doc bug has been filed at https://bugs.python.org/issue24711
149
+ if hide_input:
150
+ echo(None, err=err)
151
+ raise Abort() from None
152
+
153
+ if value_proc is None:
154
+ value_proc = convert_type(type, default)
155
+
156
+ prompt = _build_prompt(
157
+ text, prompt_suffix, show_default, default, show_choices, type
158
+ )
159
+
160
+ if confirmation_prompt:
161
+ if confirmation_prompt is True:
162
+ confirmation_prompt = _("Repeat for confirmation")
163
+
164
+ confirmation_prompt = _build_prompt(confirmation_prompt, prompt_suffix)
165
+
166
+ while True:
167
+ while True:
168
+ value = prompt_func(prompt)
169
+ if value:
170
+ break
171
+ elif default is not None:
172
+ value = default
173
+ break
174
+ try:
175
+ result = value_proc(value)
176
+ except UsageError as e:
177
+ if hide_input:
178
+ echo(_("Error: The value you entered was invalid."), err=err)
179
+ else:
180
+ echo(_("Error: {e.message}").format(e=e), err=err)
181
+ continue
182
+ if not confirmation_prompt:
183
+ return result
184
+ while True:
185
+ value2 = prompt_func(confirmation_prompt)
186
+ is_empty = not value and not value2
187
+ if value2 or is_empty:
188
+ break
189
+ if value == value2:
190
+ return result
191
+ echo(_("Error: The two entered values do not match."), err=err)
192
+
193
+
194
+ def confirm(
195
+ text: str,
196
+ default: bool | None = False,
197
+ abort: bool = False,
198
+ prompt_suffix: str = ": ",
199
+ show_default: bool = True,
200
+ err: bool = False,
201
+ ) -> bool:
202
+ """Prompts for confirmation (yes/no question).
203
+
204
+ If the user aborts the input by sending a interrupt signal this
205
+ function will catch it and raise a :exc:`Abort` exception.
206
+
207
+ :param text: the question to ask.
208
+ :param default: The default value to use when no input is given. If
209
+ ``None``, repeat until input is given.
210
+ :param abort: if this is set to `True` a negative answer aborts the
211
+ exception by raising :exc:`Abort`.
212
+ :param prompt_suffix: a suffix that should be added to the prompt.
213
+ :param show_default: shows or hides the default value in the prompt.
214
+ :param err: if set to true the file defaults to ``stderr`` instead of
215
+ ``stdout``, the same as with echo.
216
+
217
+ .. versionchanged:: 8.0
218
+ Repeat until input is given if ``default`` is ``None``.
219
+
220
+ .. versionadded:: 4.0
221
+ Added the ``err`` parameter.
222
+ """
223
+ prompt = _build_prompt(
224
+ text,
225
+ prompt_suffix,
226
+ show_default,
227
+ "y/n" if default is None else ("Y/n" if default else "y/N"),
228
+ )
229
+
230
+ while True:
231
+ try:
232
+ # Write the prompt separately so that we get nice
233
+ # coloring through colorama on Windows
234
+ echo(prompt.rstrip(" "), nl=False, err=err)
235
+ # Echo a space to stdout to work around an issue where
236
+ # readline causes backspace to clear the whole line.
237
+ value = visible_prompt_func(" ").lower().strip()
238
+ except (KeyboardInterrupt, EOFError):
239
+ raise Abort() from None
240
+ if value in ("y", "yes"):
241
+ rv = True
242
+ elif value in ("n", "no"):
243
+ rv = False
244
+ elif default is not None and value == "":
245
+ rv = default
246
+ else:
247
+ echo(_("Error: invalid input"), err=err)
248
+ continue
249
+ break
250
+ if abort and not rv:
251
+ raise Abort()
252
+ return rv
253
+
254
+
255
+ def echo_via_pager(
256
+ text_or_generator: cabc.Iterable[str] | t.Callable[[], cabc.Iterable[str]] | str,
257
+ color: bool | None = None,
258
+ ) -> None:
259
+ """This function takes a text and shows it via an environment specific
260
+ pager on stdout.
261
+
262
+ .. versionchanged:: 3.0
263
+ Added the `color` flag.
264
+
265
+ :param text_or_generator: the text to page, or alternatively, a
266
+ generator emitting the text to page.
267
+ :param color: controls if the pager supports ANSI colors or not. The
268
+ default is autodetection.
269
+ """
270
+ color = resolve_color_default(color)
271
+
272
+ if inspect.isgeneratorfunction(text_or_generator):
273
+ i = t.cast("t.Callable[[], cabc.Iterable[str]]", text_or_generator)()
274
+ elif isinstance(text_or_generator, str):
275
+ i = [text_or_generator]
276
+ else:
277
+ i = iter(t.cast("cabc.Iterable[str]", text_or_generator))
278
+
279
+ # convert every element of i to a text type if necessary
280
+ text_generator = (el if isinstance(el, str) else str(el) for el in i)
281
+
282
+ from ._termui_impl import pager
283
+
284
+ return pager(itertools.chain(text_generator, "\n"), color)
285
+
286
+
287
+ @t.overload
288
+ def progressbar(
289
+ *,
290
+ length: int,
291
+ label: str | None = None,
292
+ hidden: bool = False,
293
+ show_eta: bool = True,
294
+ show_percent: bool | None = None,
295
+ show_pos: bool = False,
296
+ fill_char: str = "#",
297
+ empty_char: str = "-",
298
+ bar_template: str = "%(label)s [%(bar)s] %(info)s",
299
+ info_sep: str = " ",
300
+ width: int = 36,
301
+ file: t.TextIO | None = None,
302
+ color: bool | None = None,
303
+ update_min_steps: int = 1,
304
+ ) -> ProgressBar[int]: ...
305
+
306
+
307
+ @t.overload
308
+ def progressbar(
309
+ iterable: cabc.Iterable[V] | None = None,
310
+ length: int | None = None,
311
+ label: str | None = None,
312
+ hidden: bool = False,
313
+ show_eta: bool = True,
314
+ show_percent: bool | None = None,
315
+ show_pos: bool = False,
316
+ item_show_func: t.Callable[[V | None], str | None] | None = None,
317
+ fill_char: str = "#",
318
+ empty_char: str = "-",
319
+ bar_template: str = "%(label)s [%(bar)s] %(info)s",
320
+ info_sep: str = " ",
321
+ width: int = 36,
322
+ file: t.TextIO | None = None,
323
+ color: bool | None = None,
324
+ update_min_steps: int = 1,
325
+ ) -> ProgressBar[V]: ...
326
+
327
+
328
+ def progressbar(
329
+ iterable: cabc.Iterable[V] | None = None,
330
+ length: int | None = None,
331
+ label: str | None = None,
332
+ hidden: bool = False,
333
+ show_eta: bool = True,
334
+ show_percent: bool | None = None,
335
+ show_pos: bool = False,
336
+ item_show_func: t.Callable[[V | None], str | None] | None = None,
337
+ fill_char: str = "#",
338
+ empty_char: str = "-",
339
+ bar_template: str = "%(label)s [%(bar)s] %(info)s",
340
+ info_sep: str = " ",
341
+ width: int = 36,
342
+ file: t.TextIO | None = None,
343
+ color: bool | None = None,
344
+ update_min_steps: int = 1,
345
+ ) -> ProgressBar[V]:
346
+ """This function creates an iterable context manager that can be used
347
+ to iterate over something while showing a progress bar. It will
348
+ either iterate over the `iterable` or `length` items (that are counted
349
+ up). While iteration happens, this function will print a rendered
350
+ progress bar to the given `file` (defaults to stdout) and will attempt
351
+ to calculate remaining time and more. By default, this progress bar
352
+ will not be rendered if the file is not a terminal.
353
+
354
+ The context manager creates the progress bar. When the context
355
+ manager is entered the progress bar is already created. With every
356
+ iteration over the progress bar, the iterable passed to the bar is
357
+ advanced and the bar is updated. When the context manager exits,
358
+ a newline is printed and the progress bar is finalized on screen.
359
+
360
+ Note: The progress bar is currently designed for use cases where the
361
+ total progress can be expected to take at least several seconds.
362
+ Because of this, the ProgressBar class object won't display
363
+ progress that is considered too fast, and progress where the time
364
+ between steps is less than a second.
365
+
366
+ No printing must happen or the progress bar will be unintentionally
367
+ destroyed.
368
+
369
+ Example usage::
370
+
371
+ with progressbar(items) as bar:
372
+ for item in bar:
373
+ do_something_with(item)
374
+
375
+ Alternatively, if no iterable is specified, one can manually update the
376
+ progress bar through the `update()` method instead of directly
377
+ iterating over the progress bar. The update method accepts the number
378
+ of steps to increment the bar with::
379
+
380
+ with progressbar(length=chunks.total_bytes) as bar:
381
+ for chunk in chunks:
382
+ process_chunk(chunk)
383
+ bar.update(chunks.bytes)
384
+
385
+ The ``update()`` method also takes an optional value specifying the
386
+ ``current_item`` at the new position. This is useful when used
387
+ together with ``item_show_func`` to customize the output for each
388
+ manual step::
389
+
390
+ with click.progressbar(
391
+ length=total_size,
392
+ label='Unzipping archive',
393
+ item_show_func=lambda a: a.filename
394
+ ) as bar:
395
+ for archive in zip_file:
396
+ archive.extract()
397
+ bar.update(archive.size, archive)
398
+
399
+ :param iterable: an iterable to iterate over. If not provided the length
400
+ is required.
401
+ :param length: the number of items to iterate over. By default the
402
+ progressbar will attempt to ask the iterator about its
403
+ length, which might or might not work. If an iterable is
404
+ also provided this parameter can be used to override the
405
+ length. If an iterable is not provided the progress bar
406
+ will iterate over a range of that length.
407
+ :param label: the label to show next to the progress bar.
408
+ :param hidden: hide the progressbar. Defaults to ``False``. When no tty is
409
+ detected, it will only print the progressbar label. Setting this to
410
+ ``False`` also disables that.
411
+ :param show_eta: enables or disables the estimated time display. This is
412
+ automatically disabled if the length cannot be
413
+ determined.
414
+ :param show_percent: enables or disables the percentage display. The
415
+ default is `True` if the iterable has a length or
416
+ `False` if not.
417
+ :param show_pos: enables or disables the absolute position display. The
418
+ default is `False`.
419
+ :param item_show_func: A function called with the current item which
420
+ can return a string to show next to the progress bar. If the
421
+ function returns ``None`` nothing is shown. The current item can
422
+ be ``None``, such as when entering and exiting the bar.
423
+ :param fill_char: the character to use to show the filled part of the
424
+ progress bar.
425
+ :param empty_char: the character to use to show the non-filled part of
426
+ the progress bar.
427
+ :param bar_template: the format string to use as template for the bar.
428
+ The parameters in it are ``label`` for the label,
429
+ ``bar`` for the progress bar and ``info`` for the
430
+ info section.
431
+ :param info_sep: the separator between multiple info items (eta etc.)
432
+ :param width: the width of the progress bar in characters, 0 means full
433
+ terminal width
434
+ :param file: The file to write to. If this is not a terminal then
435
+ only the label is printed.
436
+ :param color: controls if the terminal supports ANSI colors or not. The
437
+ default is autodetection. This is only needed if ANSI
438
+ codes are included anywhere in the progress bar output
439
+ which is not the case by default.
440
+ :param update_min_steps: Render only when this many updates have
441
+ completed. This allows tuning for very fast iterators.
442
+
443
+ .. versionadded:: 8.2
444
+ The ``hidden`` argument.
445
+
446
+ .. versionchanged:: 8.0
447
+ Output is shown even if execution time is less than 0.5 seconds.
448
+
449
+ .. versionchanged:: 8.0
450
+ ``item_show_func`` shows the current item, not the previous one.
451
+
452
+ .. versionchanged:: 8.0
453
+ Labels are echoed if the output is not a TTY. Reverts a change
454
+ in 7.0 that removed all output.
455
+
456
+ .. versionadded:: 8.0
457
+ The ``update_min_steps`` parameter.
458
+
459
+ .. versionadded:: 4.0
460
+ The ``color`` parameter and ``update`` method.
461
+
462
+ .. versionadded:: 2.0
463
+ """
464
+ from ._termui_impl import ProgressBar
465
+
466
+ color = resolve_color_default(color)
467
+ return ProgressBar(
468
+ iterable=iterable,
469
+ length=length,
470
+ hidden=hidden,
471
+ show_eta=show_eta,
472
+ show_percent=show_percent,
473
+ show_pos=show_pos,
474
+ item_show_func=item_show_func,
475
+ fill_char=fill_char,
476
+ empty_char=empty_char,
477
+ bar_template=bar_template,
478
+ info_sep=info_sep,
479
+ file=file,
480
+ label=label,
481
+ width=width,
482
+ color=color,
483
+ update_min_steps=update_min_steps,
484
+ )
485
+
486
+
487
+ def clear() -> None:
488
+ """Clears the terminal screen. This will have the effect of clearing
489
+ the whole visible space of the terminal and moving the cursor to the
490
+ top left. This does not do anything if not connected to a terminal.
491
+
492
+ .. versionadded:: 2.0
493
+ """
494
+ if not isatty(sys.stdout):
495
+ return
496
+
497
+ # ANSI escape \033[2J clears the screen, \033[1;1H moves the cursor
498
+ echo("\033[2J\033[1;1H", nl=False)
499
+
500
+
501
+ def _interpret_color(color: int | tuple[int, int, int] | str, offset: int = 0) -> str:
502
+ if isinstance(color, int):
503
+ return f"{38 + offset};5;{color:d}"
504
+
505
+ if isinstance(color, (tuple, list)):
506
+ r, g, b = color
507
+ return f"{38 + offset};2;{r:d};{g:d};{b:d}"
508
+
509
+ return str(_ansi_colors[color] + offset)
510
+
511
+
512
+ def style(
513
+ text: t.Any,
514
+ fg: int | tuple[int, int, int] | str | None = None,
515
+ bg: int | tuple[int, int, int] | str | None = None,
516
+ bold: bool | None = None,
517
+ dim: bool | None = None,
518
+ underline: bool | None = None,
519
+ overline: bool | None = None,
520
+ italic: bool | None = None,
521
+ blink: bool | None = None,
522
+ reverse: bool | None = None,
523
+ strikethrough: bool | None = None,
524
+ reset: bool = True,
525
+ ) -> str:
526
+ """Styles a text with ANSI styles and returns the new string. By
527
+ default the styling is self contained which means that at the end
528
+ of the string a reset code is issued. This can be prevented by
529
+ passing ``reset=False``.
530
+
531
+ Examples::
532
+
533
+ click.echo(click.style('Hello World!', fg='green'))
534
+ click.echo(click.style('ATTENTION!', blink=True))
535
+ click.echo(click.style('Some things', reverse=True, fg='cyan'))
536
+ click.echo(click.style('More colors', fg=(255, 12, 128), bg=117))
537
+
538
+ Supported color names:
539
+
540
+ * ``black`` (might be a gray)
541
+ * ``red``
542
+ * ``green``
543
+ * ``yellow`` (might be an orange)
544
+ * ``blue``
545
+ * ``magenta``
546
+ * ``cyan``
547
+ * ``white`` (might be light gray)
548
+ * ``bright_black``
549
+ * ``bright_red``
550
+ * ``bright_green``
551
+ * ``bright_yellow``
552
+ * ``bright_blue``
553
+ * ``bright_magenta``
554
+ * ``bright_cyan``
555
+ * ``bright_white``
556
+ * ``reset`` (reset the color code only)
557
+
558
+ If the terminal supports it, color may also be specified as:
559
+
560
+ - An integer in the interval [0, 255]. The terminal must support
561
+ 8-bit/256-color mode.
562
+ - An RGB tuple of three integers in [0, 255]. The terminal must
563
+ support 24-bit/true-color mode.
564
+
565
+ See https://en.wikipedia.org/wiki/ANSI_color and
566
+ https://gist.github.com/XVilka/8346728 for more information.
567
+
568
+ :param text: the string to style with ansi codes.
569
+ :param fg: if provided this will become the foreground color.
570
+ :param bg: if provided this will become the background color.
571
+ :param bold: if provided this will enable or disable bold mode.
572
+ :param dim: if provided this will enable or disable dim mode. This is
573
+ badly supported.
574
+ :param underline: if provided this will enable or disable underline.
575
+ :param overline: if provided this will enable or disable overline.
576
+ :param italic: if provided this will enable or disable italic.
577
+ :param blink: if provided this will enable or disable blinking.
578
+ :param reverse: if provided this will enable or disable inverse
579
+ rendering (foreground becomes background and the
580
+ other way round).
581
+ :param strikethrough: if provided this will enable or disable
582
+ striking through text.
583
+ :param reset: by default a reset-all code is added at the end of the
584
+ string which means that styles do not carry over. This
585
+ can be disabled to compose styles.
586
+
587
+ .. versionchanged:: 8.0
588
+ A non-string ``message`` is converted to a string.
589
+
590
+ .. versionchanged:: 8.0
591
+ Added support for 256 and RGB color codes.
592
+
593
+ .. versionchanged:: 8.0
594
+ Added the ``strikethrough``, ``italic``, and ``overline``
595
+ parameters.
596
+
597
+ .. versionchanged:: 7.0
598
+ Added support for bright colors.
599
+
600
+ .. versionadded:: 2.0
601
+ """
602
+ if not isinstance(text, str):
603
+ text = str(text)
604
+
605
+ bits = []
606
+
607
+ if fg:
608
+ try:
609
+ bits.append(f"\033[{_interpret_color(fg)}m")
610
+ except KeyError:
611
+ raise TypeError(f"Unknown color {fg!r}") from None
612
+
613
+ if bg:
614
+ try:
615
+ bits.append(f"\033[{_interpret_color(bg, 10)}m")
616
+ except KeyError:
617
+ raise TypeError(f"Unknown color {bg!r}") from None
618
+
619
+ if bold is not None:
620
+ bits.append(f"\033[{1 if bold else 22}m")
621
+ if dim is not None:
622
+ bits.append(f"\033[{2 if dim else 22}m")
623
+ if underline is not None:
624
+ bits.append(f"\033[{4 if underline else 24}m")
625
+ if overline is not None:
626
+ bits.append(f"\033[{53 if overline else 55}m")
627
+ if italic is not None:
628
+ bits.append(f"\033[{3 if italic else 23}m")
629
+ if blink is not None:
630
+ bits.append(f"\033[{5 if blink else 25}m")
631
+ if reverse is not None:
632
+ bits.append(f"\033[{7 if reverse else 27}m")
633
+ if strikethrough is not None:
634
+ bits.append(f"\033[{9 if strikethrough else 29}m")
635
+ bits.append(text)
636
+ if reset:
637
+ bits.append(_ansi_reset_all)
638
+ return "".join(bits)
639
+
640
+
641
+ def unstyle(text: str) -> str:
642
+ """Removes ANSI styling information from a string. Usually it's not
643
+ necessary to use this function as Click's echo function will
644
+ automatically remove styling if necessary.
645
+
646
+ .. versionadded:: 2.0
647
+
648
+ :param text: the text to remove style information from.
649
+ """
650
+ return strip_ansi(text)
651
+
652
+
653
+ def secho(
654
+ message: t.Any | None = None,
655
+ file: t.IO[t.AnyStr] | None = None,
656
+ nl: bool = True,
657
+ err: bool = False,
658
+ color: bool | None = None,
659
+ **styles: t.Any,
660
+ ) -> None:
661
+ """This function combines :func:`echo` and :func:`style` into one
662
+ call. As such the following two calls are the same::
663
+
664
+ click.secho('Hello World!', fg='green')
665
+ click.echo(click.style('Hello World!', fg='green'))
666
+
667
+ All keyword arguments are forwarded to the underlying functions
668
+ depending on which one they go with.
669
+
670
+ Non-string types will be converted to :class:`str`. However,
671
+ :class:`bytes` are passed directly to :meth:`echo` without applying
672
+ style. If you want to style bytes that represent text, call
673
+ :meth:`bytes.decode` first.
674
+
675
+ .. versionchanged:: 8.0
676
+ A non-string ``message`` is converted to a string. Bytes are
677
+ passed through without style applied.
678
+
679
+ .. versionadded:: 2.0
680
+ """
681
+ if message is not None and not isinstance(message, (bytes, bytearray)):
682
+ message = style(message, **styles)
683
+
684
+ return echo(message, file=file, nl=nl, err=err, color=color)
685
+
686
+
687
+ @t.overload
688
+ def edit(
689
+ text: bytes | bytearray,
690
+ editor: str | None = None,
691
+ env: cabc.Mapping[str, str] | None = None,
692
+ require_save: bool = False,
693
+ extension: str = ".txt",
694
+ ) -> bytes | None: ...
695
+
696
+
697
+ @t.overload
698
+ def edit(
699
+ text: str,
700
+ editor: str | None = None,
701
+ env: cabc.Mapping[str, str] | None = None,
702
+ require_save: bool = True,
703
+ extension: str = ".txt",
704
+ ) -> str | None: ...
705
+
706
+
707
+ @t.overload
708
+ def edit(
709
+ text: None = None,
710
+ editor: str | None = None,
711
+ env: cabc.Mapping[str, str] | None = None,
712
+ require_save: bool = True,
713
+ extension: str = ".txt",
714
+ filename: str | cabc.Iterable[str] | None = None,
715
+ ) -> None: ...
716
+
717
+
718
+ def edit(
719
+ text: str | bytes | bytearray | None = None,
720
+ editor: str | None = None,
721
+ env: cabc.Mapping[str, str] | None = None,
722
+ require_save: bool = True,
723
+ extension: str = ".txt",
724
+ filename: str | cabc.Iterable[str] | None = None,
725
+ ) -> str | bytes | bytearray | None:
726
+ r"""Edits the given text in the defined editor. If an editor is given
727
+ (should be the full path to the executable but the regular operating
728
+ system search path is used for finding the executable) it overrides
729
+ the detected editor. Optionally, some environment variables can be
730
+ used. If the editor is closed without changes, `None` is returned. In
731
+ case a file is edited directly the return value is always `None` and
732
+ `require_save` and `extension` are ignored.
733
+
734
+ If the editor cannot be opened a :exc:`UsageError` is raised.
735
+
736
+ Note for Windows: to simplify cross-platform usage, the newlines are
737
+ automatically converted from POSIX to Windows and vice versa. As such,
738
+ the message here will have ``\n`` as newline markers.
739
+
740
+ :param text: the text to edit.
741
+ :param editor: optionally the editor to use. Defaults to automatic
742
+ detection.
743
+ :param env: environment variables to forward to the editor.
744
+ :param require_save: if this is true, then not saving in the editor
745
+ will make the return value become `None`.
746
+ :param extension: the extension to tell the editor about. This defaults
747
+ to `.txt` but changing this might change syntax
748
+ highlighting.
749
+ :param filename: if provided it will edit this file instead of the
750
+ provided text contents. It will not use a temporary
751
+ file as an indirection in that case. If the editor supports
752
+ editing multiple files at once, a sequence of files may be
753
+ passed as well. Invoke `click.file` once per file instead
754
+ if multiple files cannot be managed at once or editing the
755
+ files serially is desired.
756
+
757
+ .. versionchanged:: 8.2.0
758
+ ``filename`` now accepts any ``Iterable[str]`` in addition to a ``str``
759
+ if the ``editor`` supports editing multiple files at once.
760
+
761
+ """
762
+ from ._termui_impl import Editor
763
+
764
+ ed = Editor(editor=editor, env=env, require_save=require_save, extension=extension)
765
+
766
+ if filename is None:
767
+ return ed.edit(text)
768
+
769
+ if isinstance(filename, str):
770
+ filename = (filename,)
771
+
772
+ ed.edit_files(filenames=filename)
773
+ return None
774
+
775
+
776
+ def launch(url: str, wait: bool = False, locate: bool = False) -> int:
777
+ """This function launches the given URL (or filename) in the default
778
+ viewer application for this file type. If this is an executable, it
779
+ might launch the executable in a new session. The return value is
780
+ the exit code of the launched application. Usually, ``0`` indicates
781
+ success.
782
+
783
+ Examples::
784
+
785
+ click.launch('https://click.palletsprojects.com/')
786
+ click.launch('/my/downloaded/file', locate=True)
787
+
788
+ .. versionadded:: 2.0
789
+
790
+ :param url: URL or filename of the thing to launch.
791
+ :param wait: Wait for the program to exit before returning. This
792
+ only works if the launched program blocks. In particular,
793
+ ``xdg-open`` on Linux does not block.
794
+ :param locate: if this is set to `True` then instead of launching the
795
+ application associated with the URL it will attempt to
796
+ launch a file manager with the file located. This
797
+ might have weird effects if the URL does not point to
798
+ the filesystem.
799
+ """
800
+ from ._termui_impl import open_url
801
+
802
+ return open_url(url, wait=wait, locate=locate)
803
+
804
+
805
+ # If this is provided, getchar() calls into this instead. This is used
806
+ # for unittesting purposes.
807
+ _getchar: t.Callable[[bool], str] | None = None
808
+
809
+
810
+ def getchar(echo: bool = False) -> str:
811
+ """Fetches a single character from the terminal and returns it. This
812
+ will always return a unicode character and under certain rare
813
+ circumstances this might return more than one character. The
814
+ situations which more than one character is returned is when for
815
+ whatever reason multiple characters end up in the terminal buffer or
816
+ standard input was not actually a terminal.
817
+
818
+ Note that this will always read from the terminal, even if something
819
+ is piped into the standard input.
820
+
821
+ Note for Windows: in rare cases when typing non-ASCII characters, this
822
+ function might wait for a second character and then return both at once.
823
+ This is because certain Unicode characters look like special-key markers.
824
+
825
+ .. versionadded:: 2.0
826
+
827
+ :param echo: if set to `True`, the character read will also show up on
828
+ the terminal. The default is to not show it.
829
+ """
830
+ global _getchar
831
+
832
+ if _getchar is None:
833
+ from ._termui_impl import getchar as f
834
+
835
+ _getchar = f
836
+
837
+ return _getchar(echo)
838
+
839
+
840
+ def raw_terminal() -> AbstractContextManager[int]:
841
+ from ._termui_impl import raw_terminal as f
842
+
843
+ return f()
844
+
845
+
846
+ def pause(info: str | None = None, err: bool = False) -> None:
847
+ """This command stops execution and waits for the user to press any
848
+ key to continue. This is similar to the Windows batch "pause"
849
+ command. If the program is not run through a terminal, this command
850
+ will instead do nothing.
851
+
852
+ .. versionadded:: 2.0
853
+
854
+ .. versionadded:: 4.0
855
+ Added the `err` parameter.
856
+
857
+ :param info: The message to print before pausing. Defaults to
858
+ ``"Press any key to continue..."``.
859
+ :param err: if set to message goes to ``stderr`` instead of
860
+ ``stdout``, the same as with echo.
861
+ """
862
+ if not isatty(sys.stdin) or not isatty(sys.stdout):
863
+ return
864
+
865
+ if info is None:
866
+ info = _("Press any key to continue...")
867
+
868
+ try:
869
+ if info:
870
+ echo(info, nl=False, err=err)
871
+ try:
872
+ getchar()
873
+ except (KeyboardInterrupt, EOFError):
874
+ pass
875
+ finally:
876
+ if info:
877
+ echo(err=err)
lib/python3.12/site-packages/click/testing.py ADDED
@@ -0,0 +1,565 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import collections.abc as cabc
4
+ import contextlib
5
+ import io
6
+ import os
7
+ import shlex
8
+ import shutil
9
+ import sys
10
+ import tempfile
11
+ import typing as t
12
+ from types import TracebackType
13
+
14
+ from . import _compat
15
+ from . import formatting
16
+ from . import termui
17
+ from . import utils
18
+ from ._compat import _find_binary_reader
19
+
20
+ if t.TYPE_CHECKING:
21
+ from _typeshed import ReadableBuffer
22
+
23
+ from .core import Command
24
+
25
+
26
+ class EchoingStdin:
27
+ def __init__(self, input: t.BinaryIO, output: t.BinaryIO) -> None:
28
+ self._input = input
29
+ self._output = output
30
+ self._paused = False
31
+
32
+ def __getattr__(self, x: str) -> t.Any:
33
+ return getattr(self._input, x)
34
+
35
+ def _echo(self, rv: bytes) -> bytes:
36
+ if not self._paused:
37
+ self._output.write(rv)
38
+
39
+ return rv
40
+
41
+ def read(self, n: int = -1) -> bytes:
42
+ return self._echo(self._input.read(n))
43
+
44
+ def read1(self, n: int = -1) -> bytes:
45
+ return self._echo(self._input.read1(n)) # type: ignore
46
+
47
+ def readline(self, n: int = -1) -> bytes:
48
+ return self._echo(self._input.readline(n))
49
+
50
+ def readlines(self) -> list[bytes]:
51
+ return [self._echo(x) for x in self._input.readlines()]
52
+
53
+ def __iter__(self) -> cabc.Iterator[bytes]:
54
+ return iter(self._echo(x) for x in self._input)
55
+
56
+ def __repr__(self) -> str:
57
+ return repr(self._input)
58
+
59
+
60
+ @contextlib.contextmanager
61
+ def _pause_echo(stream: EchoingStdin | None) -> cabc.Iterator[None]:
62
+ if stream is None:
63
+ yield
64
+ else:
65
+ stream._paused = True
66
+ yield
67
+ stream._paused = False
68
+
69
+
70
+ class BytesIOCopy(io.BytesIO):
71
+ """Patch ``io.BytesIO`` to let the written stream be copied to another.
72
+
73
+ .. versionadded:: 8.2
74
+ """
75
+
76
+ def __init__(self, copy_to: io.BytesIO) -> None:
77
+ super().__init__()
78
+ self.copy_to = copy_to
79
+
80
+ def flush(self) -> None:
81
+ super().flush()
82
+ self.copy_to.flush()
83
+
84
+ def write(self, b: ReadableBuffer) -> int:
85
+ self.copy_to.write(b)
86
+ return super().write(b)
87
+
88
+
89
+ class StreamMixer:
90
+ """Mixes `<stdout>` and `<stderr>` streams.
91
+
92
+ The result is available in the ``output`` attribute.
93
+
94
+ .. versionadded:: 8.2
95
+ """
96
+
97
+ def __init__(self) -> None:
98
+ self.output: io.BytesIO = io.BytesIO()
99
+ self.stdout: io.BytesIO = BytesIOCopy(copy_to=self.output)
100
+ self.stderr: io.BytesIO = BytesIOCopy(copy_to=self.output)
101
+
102
+
103
+ class _NamedTextIOWrapper(io.TextIOWrapper):
104
+ def __init__(
105
+ self, buffer: t.BinaryIO, name: str, mode: str, **kwargs: t.Any
106
+ ) -> None:
107
+ super().__init__(buffer, **kwargs)
108
+ self._name = name
109
+ self._mode = mode
110
+
111
+ @property
112
+ def name(self) -> str:
113
+ return self._name
114
+
115
+ @property
116
+ def mode(self) -> str:
117
+ return self._mode
118
+
119
+ def __next__(self) -> str: # type: ignore
120
+ try:
121
+ line = super().__next__()
122
+ except StopIteration as e:
123
+ raise EOFError() from e
124
+ return line
125
+
126
+
127
+ def make_input_stream(
128
+ input: str | bytes | t.IO[t.Any] | None, charset: str
129
+ ) -> t.BinaryIO:
130
+ # Is already an input stream.
131
+ if hasattr(input, "read"):
132
+ rv = _find_binary_reader(t.cast("t.IO[t.Any]", input))
133
+
134
+ if rv is not None:
135
+ return rv
136
+
137
+ raise TypeError("Could not find binary reader for input stream.")
138
+
139
+ if input is None:
140
+ input = b""
141
+ elif isinstance(input, str):
142
+ input = input.encode(charset)
143
+
144
+ return io.BytesIO(input)
145
+
146
+
147
+ class Result:
148
+ """Holds the captured result of an invoked CLI script.
149
+
150
+ :param runner: The runner that created the result
151
+ :param stdout_bytes: The standard output as bytes.
152
+ :param stderr_bytes: The standard error as bytes.
153
+ :param output_bytes: A mix of ``stdout_bytes`` and ``stderr_bytes``, as the
154
+ user would see it in its terminal.
155
+ :param return_value: The value returned from the invoked command.
156
+ :param exit_code: The exit code as integer.
157
+ :param exception: The exception that happened if one did.
158
+ :param exc_info: Exception information (exception type, exception instance,
159
+ traceback type).
160
+
161
+ .. versionchanged:: 8.2
162
+ ``stderr_bytes`` no longer optional, ``output_bytes`` introduced and
163
+ ``mix_stderr`` has been removed.
164
+
165
+ .. versionadded:: 8.0
166
+ Added ``return_value``.
167
+ """
168
+
169
+ def __init__(
170
+ self,
171
+ runner: CliRunner,
172
+ stdout_bytes: bytes,
173
+ stderr_bytes: bytes,
174
+ output_bytes: bytes,
175
+ return_value: t.Any,
176
+ exit_code: int,
177
+ exception: BaseException | None,
178
+ exc_info: tuple[type[BaseException], BaseException, TracebackType]
179
+ | None = None,
180
+ ):
181
+ self.runner = runner
182
+ self.stdout_bytes = stdout_bytes
183
+ self.stderr_bytes = stderr_bytes
184
+ self.output_bytes = output_bytes
185
+ self.return_value = return_value
186
+ self.exit_code = exit_code
187
+ self.exception = exception
188
+ self.exc_info = exc_info
189
+
190
+ @property
191
+ def output(self) -> str:
192
+ """The terminal output as unicode string, as the user would see it.
193
+
194
+ .. versionchanged:: 8.2
195
+ No longer a proxy for ``self.stdout``. Now has its own independent stream
196
+ that is mixing `<stdout>` and `<stderr>`, in the order they were written.
197
+ """
198
+ return self.output_bytes.decode(self.runner.charset, "replace").replace(
199
+ "\r\n", "\n"
200
+ )
201
+
202
+ @property
203
+ def stdout(self) -> str:
204
+ """The standard output as unicode string."""
205
+ return self.stdout_bytes.decode(self.runner.charset, "replace").replace(
206
+ "\r\n", "\n"
207
+ )
208
+
209
+ @property
210
+ def stderr(self) -> str:
211
+ """The standard error as unicode string.
212
+
213
+ .. versionchanged:: 8.2
214
+ No longer raise an exception, always returns the `<stderr>` string.
215
+ """
216
+ return self.stderr_bytes.decode(self.runner.charset, "replace").replace(
217
+ "\r\n", "\n"
218
+ )
219
+
220
+ def __repr__(self) -> str:
221
+ exc_str = repr(self.exception) if self.exception else "okay"
222
+ return f"<{type(self).__name__} {exc_str}>"
223
+
224
+
225
+ class CliRunner:
226
+ """The CLI runner provides functionality to invoke a Click command line
227
+ script for unittesting purposes in a isolated environment. This only
228
+ works in single-threaded systems without any concurrency as it changes the
229
+ global interpreter state.
230
+
231
+ :param charset: the character set for the input and output data.
232
+ :param env: a dictionary with environment variables for overriding.
233
+ :param echo_stdin: if this is set to `True`, then reading from `<stdin>` writes
234
+ to `<stdout>`. This is useful for showing examples in
235
+ some circumstances. Note that regular prompts
236
+ will automatically echo the input.
237
+ :param catch_exceptions: Whether to catch any exceptions other than
238
+ ``SystemExit`` when running :meth:`~CliRunner.invoke`.
239
+
240
+ .. versionchanged:: 8.2
241
+ Added the ``catch_exceptions`` parameter.
242
+
243
+ .. versionchanged:: 8.2
244
+ ``mix_stderr`` parameter has been removed.
245
+ """
246
+
247
+ def __init__(
248
+ self,
249
+ charset: str = "utf-8",
250
+ env: cabc.Mapping[str, str | None] | None = None,
251
+ echo_stdin: bool = False,
252
+ catch_exceptions: bool = True,
253
+ ) -> None:
254
+ self.charset = charset
255
+ self.env: cabc.Mapping[str, str | None] = env or {}
256
+ self.echo_stdin = echo_stdin
257
+ self.catch_exceptions = catch_exceptions
258
+
259
+ def get_default_prog_name(self, cli: Command) -> str:
260
+ """Given a command object it will return the default program name
261
+ for it. The default is the `name` attribute or ``"root"`` if not
262
+ set.
263
+ """
264
+ return cli.name or "root"
265
+
266
+ def make_env(
267
+ self, overrides: cabc.Mapping[str, str | None] | None = None
268
+ ) -> cabc.Mapping[str, str | None]:
269
+ """Returns the environment overrides for invoking a script."""
270
+ rv = dict(self.env)
271
+ if overrides:
272
+ rv.update(overrides)
273
+ return rv
274
+
275
+ @contextlib.contextmanager
276
+ def isolation(
277
+ self,
278
+ input: str | bytes | t.IO[t.Any] | None = None,
279
+ env: cabc.Mapping[str, str | None] | None = None,
280
+ color: bool = False,
281
+ ) -> cabc.Iterator[tuple[io.BytesIO, io.BytesIO, io.BytesIO]]:
282
+ """A context manager that sets up the isolation for invoking of a
283
+ command line tool. This sets up `<stdin>` with the given input data
284
+ and `os.environ` with the overrides from the given dictionary.
285
+ This also rebinds some internals in Click to be mocked (like the
286
+ prompt functionality).
287
+
288
+ This is automatically done in the :meth:`invoke` method.
289
+
290
+ :param input: the input stream to put into `sys.stdin`.
291
+ :param env: the environment overrides as dictionary.
292
+ :param color: whether the output should contain color codes. The
293
+ application can still override this explicitly.
294
+
295
+ .. versionadded:: 8.2
296
+ An additional output stream is returned, which is a mix of
297
+ `<stdout>` and `<stderr>` streams.
298
+
299
+ .. versionchanged:: 8.2
300
+ Always returns the `<stderr>` stream.
301
+
302
+ .. versionchanged:: 8.0
303
+ `<stderr>` is opened with ``errors="backslashreplace"``
304
+ instead of the default ``"strict"``.
305
+
306
+ .. versionchanged:: 4.0
307
+ Added the ``color`` parameter.
308
+ """
309
+ bytes_input = make_input_stream(input, self.charset)
310
+ echo_input = None
311
+
312
+ old_stdin = sys.stdin
313
+ old_stdout = sys.stdout
314
+ old_stderr = sys.stderr
315
+ old_forced_width = formatting.FORCED_WIDTH
316
+ formatting.FORCED_WIDTH = 80
317
+
318
+ env = self.make_env(env)
319
+
320
+ stream_mixer = StreamMixer()
321
+
322
+ if self.echo_stdin:
323
+ bytes_input = echo_input = t.cast(
324
+ t.BinaryIO, EchoingStdin(bytes_input, stream_mixer.stdout)
325
+ )
326
+
327
+ sys.stdin = text_input = _NamedTextIOWrapper(
328
+ bytes_input, encoding=self.charset, name="<stdin>", mode="r"
329
+ )
330
+
331
+ if self.echo_stdin:
332
+ # Force unbuffered reads, otherwise TextIOWrapper reads a
333
+ # large chunk which is echoed early.
334
+ text_input._CHUNK_SIZE = 1 # type: ignore
335
+
336
+ sys.stdout = _NamedTextIOWrapper(
337
+ stream_mixer.stdout, encoding=self.charset, name="<stdout>", mode="w"
338
+ )
339
+
340
+ sys.stderr = _NamedTextIOWrapper(
341
+ stream_mixer.stderr,
342
+ encoding=self.charset,
343
+ name="<stderr>",
344
+ mode="w",
345
+ errors="backslashreplace",
346
+ )
347
+
348
+ @_pause_echo(echo_input) # type: ignore
349
+ def visible_input(prompt: str | None = None) -> str:
350
+ sys.stdout.write(prompt or "")
351
+ val = next(text_input).rstrip("\r\n")
352
+ sys.stdout.write(f"{val}\n")
353
+ sys.stdout.flush()
354
+ return val
355
+
356
+ @_pause_echo(echo_input) # type: ignore
357
+ def hidden_input(prompt: str | None = None) -> str:
358
+ sys.stdout.write(f"{prompt or ''}\n")
359
+ sys.stdout.flush()
360
+ return next(text_input).rstrip("\r\n")
361
+
362
+ @_pause_echo(echo_input) # type: ignore
363
+ def _getchar(echo: bool) -> str:
364
+ char = sys.stdin.read(1)
365
+
366
+ if echo:
367
+ sys.stdout.write(char)
368
+
369
+ sys.stdout.flush()
370
+ return char
371
+
372
+ default_color = color
373
+
374
+ def should_strip_ansi(
375
+ stream: t.IO[t.Any] | None = None, color: bool | None = None
376
+ ) -> bool:
377
+ if color is None:
378
+ return not default_color
379
+ return not color
380
+
381
+ old_visible_prompt_func = termui.visible_prompt_func
382
+ old_hidden_prompt_func = termui.hidden_prompt_func
383
+ old__getchar_func = termui._getchar
384
+ old_should_strip_ansi = utils.should_strip_ansi # type: ignore
385
+ old__compat_should_strip_ansi = _compat.should_strip_ansi
386
+ termui.visible_prompt_func = visible_input
387
+ termui.hidden_prompt_func = hidden_input
388
+ termui._getchar = _getchar
389
+ utils.should_strip_ansi = should_strip_ansi # type: ignore
390
+ _compat.should_strip_ansi = should_strip_ansi
391
+
392
+ old_env = {}
393
+ try:
394
+ for key, value in env.items():
395
+ old_env[key] = os.environ.get(key)
396
+ if value is None:
397
+ try:
398
+ del os.environ[key]
399
+ except Exception:
400
+ pass
401
+ else:
402
+ os.environ[key] = value
403
+ yield (stream_mixer.stdout, stream_mixer.stderr, stream_mixer.output)
404
+ finally:
405
+ for key, value in old_env.items():
406
+ if value is None:
407
+ try:
408
+ del os.environ[key]
409
+ except Exception:
410
+ pass
411
+ else:
412
+ os.environ[key] = value
413
+ sys.stdout = old_stdout
414
+ sys.stderr = old_stderr
415
+ sys.stdin = old_stdin
416
+ termui.visible_prompt_func = old_visible_prompt_func
417
+ termui.hidden_prompt_func = old_hidden_prompt_func
418
+ termui._getchar = old__getchar_func
419
+ utils.should_strip_ansi = old_should_strip_ansi # type: ignore
420
+ _compat.should_strip_ansi = old__compat_should_strip_ansi
421
+ formatting.FORCED_WIDTH = old_forced_width
422
+
423
+ def invoke(
424
+ self,
425
+ cli: Command,
426
+ args: str | cabc.Sequence[str] | None = None,
427
+ input: str | bytes | t.IO[t.Any] | None = None,
428
+ env: cabc.Mapping[str, str | None] | None = None,
429
+ catch_exceptions: bool | None = None,
430
+ color: bool = False,
431
+ **extra: t.Any,
432
+ ) -> Result:
433
+ """Invokes a command in an isolated environment. The arguments are
434
+ forwarded directly to the command line script, the `extra` keyword
435
+ arguments are passed to the :meth:`~clickpkg.Command.main` function of
436
+ the command.
437
+
438
+ This returns a :class:`Result` object.
439
+
440
+ :param cli: the command to invoke
441
+ :param args: the arguments to invoke. It may be given as an iterable
442
+ or a string. When given as string it will be interpreted
443
+ as a Unix shell command. More details at
444
+ :func:`shlex.split`.
445
+ :param input: the input data for `sys.stdin`.
446
+ :param env: the environment overrides.
447
+ :param catch_exceptions: Whether to catch any other exceptions than
448
+ ``SystemExit``. If :data:`None`, the value
449
+ from :class:`CliRunner` is used.
450
+ :param extra: the keyword arguments to pass to :meth:`main`.
451
+ :param color: whether the output should contain color codes. The
452
+ application can still override this explicitly.
453
+
454
+ .. versionadded:: 8.2
455
+ The result object has the ``output_bytes`` attribute with
456
+ the mix of ``stdout_bytes`` and ``stderr_bytes``, as the user would
457
+ see it in its terminal.
458
+
459
+ .. versionchanged:: 8.2
460
+ The result object always returns the ``stderr_bytes`` stream.
461
+
462
+ .. versionchanged:: 8.0
463
+ The result object has the ``return_value`` attribute with
464
+ the value returned from the invoked command.
465
+
466
+ .. versionchanged:: 4.0
467
+ Added the ``color`` parameter.
468
+
469
+ .. versionchanged:: 3.0
470
+ Added the ``catch_exceptions`` parameter.
471
+
472
+ .. versionchanged:: 3.0
473
+ The result object has the ``exc_info`` attribute with the
474
+ traceback if available.
475
+ """
476
+ exc_info = None
477
+ if catch_exceptions is None:
478
+ catch_exceptions = self.catch_exceptions
479
+
480
+ with self.isolation(input=input, env=env, color=color) as outstreams:
481
+ return_value = None
482
+ exception: BaseException | None = None
483
+ exit_code = 0
484
+
485
+ if isinstance(args, str):
486
+ args = shlex.split(args)
487
+
488
+ try:
489
+ prog_name = extra.pop("prog_name")
490
+ except KeyError:
491
+ prog_name = self.get_default_prog_name(cli)
492
+
493
+ try:
494
+ return_value = cli.main(args=args or (), prog_name=prog_name, **extra)
495
+ except SystemExit as e:
496
+ exc_info = sys.exc_info()
497
+ e_code = t.cast("int | t.Any | None", e.code)
498
+
499
+ if e_code is None:
500
+ e_code = 0
501
+
502
+ if e_code != 0:
503
+ exception = e
504
+
505
+ if not isinstance(e_code, int):
506
+ sys.stdout.write(str(e_code))
507
+ sys.stdout.write("\n")
508
+ e_code = 1
509
+
510
+ exit_code = e_code
511
+
512
+ except Exception as e:
513
+ if not catch_exceptions:
514
+ raise
515
+ exception = e
516
+ exit_code = 1
517
+ exc_info = sys.exc_info()
518
+ finally:
519
+ sys.stdout.flush()
520
+ sys.stderr.flush()
521
+ stdout = outstreams[0].getvalue()
522
+ stderr = outstreams[1].getvalue()
523
+ output = outstreams[2].getvalue()
524
+
525
+ return Result(
526
+ runner=self,
527
+ stdout_bytes=stdout,
528
+ stderr_bytes=stderr,
529
+ output_bytes=output,
530
+ return_value=return_value,
531
+ exit_code=exit_code,
532
+ exception=exception,
533
+ exc_info=exc_info, # type: ignore
534
+ )
535
+
536
+ @contextlib.contextmanager
537
+ def isolated_filesystem(
538
+ self, temp_dir: str | os.PathLike[str] | None = None
539
+ ) -> cabc.Iterator[str]:
540
+ """A context manager that creates a temporary directory and
541
+ changes the current working directory to it. This isolates tests
542
+ that affect the contents of the CWD to prevent them from
543
+ interfering with each other.
544
+
545
+ :param temp_dir: Create the temporary directory under this
546
+ directory. If given, the created directory is not removed
547
+ when exiting.
548
+
549
+ .. versionchanged:: 8.0
550
+ Added the ``temp_dir`` parameter.
551
+ """
552
+ cwd = os.getcwd()
553
+ dt = tempfile.mkdtemp(dir=temp_dir)
554
+ os.chdir(dt)
555
+
556
+ try:
557
+ yield dt
558
+ finally:
559
+ os.chdir(cwd)
560
+
561
+ if temp_dir is None:
562
+ try:
563
+ shutil.rmtree(dt)
564
+ except OSError:
565
+ pass
lib/python3.12/site-packages/click/types.py ADDED
@@ -0,0 +1,1165 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import collections.abc as cabc
4
+ import enum
5
+ import os
6
+ import stat
7
+ import sys
8
+ import typing as t
9
+ from datetime import datetime
10
+ from gettext import gettext as _
11
+ from gettext import ngettext
12
+
13
+ from ._compat import _get_argv_encoding
14
+ from ._compat import open_stream
15
+ from .exceptions import BadParameter
16
+ from .utils import format_filename
17
+ from .utils import LazyFile
18
+ from .utils import safecall
19
+
20
+ if t.TYPE_CHECKING:
21
+ import typing_extensions as te
22
+
23
+ from .core import Context
24
+ from .core import Parameter
25
+ from .shell_completion import CompletionItem
26
+
27
+ ParamTypeValue = t.TypeVar("ParamTypeValue")
28
+
29
+
30
+ class ParamType:
31
+ """Represents the type of a parameter. Validates and converts values
32
+ from the command line or Python into the correct type.
33
+
34
+ To implement a custom type, subclass and implement at least the
35
+ following:
36
+
37
+ - The :attr:`name` class attribute must be set.
38
+ - Calling an instance of the type with ``None`` must return
39
+ ``None``. This is already implemented by default.
40
+ - :meth:`convert` must convert string values to the correct type.
41
+ - :meth:`convert` must accept values that are already the correct
42
+ type.
43
+ - It must be able to convert a value if the ``ctx`` and ``param``
44
+ arguments are ``None``. This can occur when converting prompt
45
+ input.
46
+ """
47
+
48
+ is_composite: t.ClassVar[bool] = False
49
+ arity: t.ClassVar[int] = 1
50
+
51
+ #: the descriptive name of this type
52
+ name: str
53
+
54
+ #: if a list of this type is expected and the value is pulled from a
55
+ #: string environment variable, this is what splits it up. `None`
56
+ #: means any whitespace. For all parameters the general rule is that
57
+ #: whitespace splits them up. The exception are paths and files which
58
+ #: are split by ``os.path.pathsep`` by default (":" on Unix and ";" on
59
+ #: Windows).
60
+ envvar_list_splitter: t.ClassVar[str | None] = None
61
+
62
+ def to_info_dict(self) -> dict[str, t.Any]:
63
+ """Gather information that could be useful for a tool generating
64
+ user-facing documentation.
65
+
66
+ Use :meth:`click.Context.to_info_dict` to traverse the entire
67
+ CLI structure.
68
+
69
+ .. versionadded:: 8.0
70
+ """
71
+ # The class name without the "ParamType" suffix.
72
+ param_type = type(self).__name__.partition("ParamType")[0]
73
+ param_type = param_type.partition("ParameterType")[0]
74
+
75
+ # Custom subclasses might not remember to set a name.
76
+ if hasattr(self, "name"):
77
+ name = self.name
78
+ else:
79
+ name = param_type
80
+
81
+ return {"param_type": param_type, "name": name}
82
+
83
+ def __call__(
84
+ self,
85
+ value: t.Any,
86
+ param: Parameter | None = None,
87
+ ctx: Context | None = None,
88
+ ) -> t.Any:
89
+ if value is not None:
90
+ return self.convert(value, param, ctx)
91
+
92
+ def get_metavar(self, param: Parameter, ctx: Context) -> str | None:
93
+ """Returns the metavar default for this param if it provides one."""
94
+
95
+ def get_missing_message(self, param: Parameter, ctx: Context | None) -> str | None:
96
+ """Optionally might return extra information about a missing
97
+ parameter.
98
+
99
+ .. versionadded:: 2.0
100
+ """
101
+
102
+ def convert(
103
+ self, value: t.Any, param: Parameter | None, ctx: Context | None
104
+ ) -> t.Any:
105
+ """Convert the value to the correct type. This is not called if
106
+ the value is ``None`` (the missing value).
107
+
108
+ This must accept string values from the command line, as well as
109
+ values that are already the correct type. It may also convert
110
+ other compatible types.
111
+
112
+ The ``param`` and ``ctx`` arguments may be ``None`` in certain
113
+ situations, such as when converting prompt input.
114
+
115
+ If the value cannot be converted, call :meth:`fail` with a
116
+ descriptive message.
117
+
118
+ :param value: The value to convert.
119
+ :param param: The parameter that is using this type to convert
120
+ its value. May be ``None``.
121
+ :param ctx: The current context that arrived at this value. May
122
+ be ``None``.
123
+ """
124
+ return value
125
+
126
+ def split_envvar_value(self, rv: str) -> cabc.Sequence[str]:
127
+ """Given a value from an environment variable this splits it up
128
+ into small chunks depending on the defined envvar list splitter.
129
+
130
+ If the splitter is set to `None`, which means that whitespace splits,
131
+ then leading and trailing whitespace is ignored. Otherwise, leading
132
+ and trailing splitters usually lead to empty items being included.
133
+ """
134
+ return (rv or "").split(self.envvar_list_splitter)
135
+
136
+ def fail(
137
+ self,
138
+ message: str,
139
+ param: Parameter | None = None,
140
+ ctx: Context | None = None,
141
+ ) -> t.NoReturn:
142
+ """Helper method to fail with an invalid value message."""
143
+ raise BadParameter(message, ctx=ctx, param=param)
144
+
145
+ def shell_complete(
146
+ self, ctx: Context, param: Parameter, incomplete: str
147
+ ) -> list[CompletionItem]:
148
+ """Return a list of
149
+ :class:`~click.shell_completion.CompletionItem` objects for the
150
+ incomplete value. Most types do not provide completions, but
151
+ some do, and this allows custom types to provide custom
152
+ completions as well.
153
+
154
+ :param ctx: Invocation context for this command.
155
+ :param param: The parameter that is requesting completion.
156
+ :param incomplete: Value being completed. May be empty.
157
+
158
+ .. versionadded:: 8.0
159
+ """
160
+ return []
161
+
162
+
163
+ class CompositeParamType(ParamType):
164
+ is_composite = True
165
+
166
+ @property
167
+ def arity(self) -> int: # type: ignore
168
+ raise NotImplementedError()
169
+
170
+
171
+ class FuncParamType(ParamType):
172
+ def __init__(self, func: t.Callable[[t.Any], t.Any]) -> None:
173
+ self.name: str = func.__name__
174
+ self.func = func
175
+
176
+ def to_info_dict(self) -> dict[str, t.Any]:
177
+ info_dict = super().to_info_dict()
178
+ info_dict["func"] = self.func
179
+ return info_dict
180
+
181
+ def convert(
182
+ self, value: t.Any, param: Parameter | None, ctx: Context | None
183
+ ) -> t.Any:
184
+ try:
185
+ return self.func(value)
186
+ except ValueError:
187
+ try:
188
+ value = str(value)
189
+ except UnicodeError:
190
+ value = value.decode("utf-8", "replace")
191
+
192
+ self.fail(value, param, ctx)
193
+
194
+
195
+ class UnprocessedParamType(ParamType):
196
+ name = "text"
197
+
198
+ def convert(
199
+ self, value: t.Any, param: Parameter | None, ctx: Context | None
200
+ ) -> t.Any:
201
+ return value
202
+
203
+ def __repr__(self) -> str:
204
+ return "UNPROCESSED"
205
+
206
+
207
+ class StringParamType(ParamType):
208
+ name = "text"
209
+
210
+ def convert(
211
+ self, value: t.Any, param: Parameter | None, ctx: Context | None
212
+ ) -> t.Any:
213
+ if isinstance(value, bytes):
214
+ enc = _get_argv_encoding()
215
+ try:
216
+ value = value.decode(enc)
217
+ except UnicodeError:
218
+ fs_enc = sys.getfilesystemencoding()
219
+ if fs_enc != enc:
220
+ try:
221
+ value = value.decode(fs_enc)
222
+ except UnicodeError:
223
+ value = value.decode("utf-8", "replace")
224
+ else:
225
+ value = value.decode("utf-8", "replace")
226
+ return value
227
+ return str(value)
228
+
229
+ def __repr__(self) -> str:
230
+ return "STRING"
231
+
232
+
233
+ class Choice(ParamType, t.Generic[ParamTypeValue]):
234
+ """The choice type allows a value to be checked against a fixed set
235
+ of supported values.
236
+
237
+ You may pass any iterable value which will be converted to a tuple
238
+ and thus will only be iterated once.
239
+
240
+ The resulting value will always be one of the originally passed choices.
241
+ See :meth:`normalize_choice` for more info on the mapping of strings
242
+ to choices. See :ref:`choice-opts` for an example.
243
+
244
+ :param case_sensitive: Set to false to make choices case
245
+ insensitive. Defaults to true.
246
+
247
+ .. versionchanged:: 8.2.0
248
+ Non-``str`` ``choices`` are now supported. It can additionally be any
249
+ iterable. Before you were not recommended to pass anything but a list or
250
+ tuple.
251
+
252
+ .. versionadded:: 8.2.0
253
+ Choice normalization can be overridden via :meth:`normalize_choice`.
254
+ """
255
+
256
+ name = "choice"
257
+
258
+ def __init__(
259
+ self, choices: cabc.Iterable[ParamTypeValue], case_sensitive: bool = True
260
+ ) -> None:
261
+ self.choices: cabc.Sequence[ParamTypeValue] = tuple(choices)
262
+ self.case_sensitive = case_sensitive
263
+
264
+ def to_info_dict(self) -> dict[str, t.Any]:
265
+ info_dict = super().to_info_dict()
266
+ info_dict["choices"] = self.choices
267
+ info_dict["case_sensitive"] = self.case_sensitive
268
+ return info_dict
269
+
270
+ def _normalized_mapping(
271
+ self, ctx: Context | None = None
272
+ ) -> cabc.Mapping[ParamTypeValue, str]:
273
+ """
274
+ Returns mapping where keys are the original choices and the values are
275
+ the normalized values that are accepted via the command line.
276
+
277
+ This is a simple wrapper around :meth:`normalize_choice`, use that
278
+ instead which is supported.
279
+ """
280
+ return {
281
+ choice: self.normalize_choice(
282
+ choice=choice,
283
+ ctx=ctx,
284
+ )
285
+ for choice in self.choices
286
+ }
287
+
288
+ def normalize_choice(self, choice: ParamTypeValue, ctx: Context | None) -> str:
289
+ """
290
+ Normalize a choice value, used to map a passed string to a choice.
291
+ Each choice must have a unique normalized value.
292
+
293
+ By default uses :meth:`Context.token_normalize_func` and if not case
294
+ sensitive, convert it to a casefolded value.
295
+
296
+ .. versionadded:: 8.2.0
297
+ """
298
+ normed_value = choice.name if isinstance(choice, enum.Enum) else str(choice)
299
+
300
+ if ctx is not None and ctx.token_normalize_func is not None:
301
+ normed_value = ctx.token_normalize_func(normed_value)
302
+
303
+ if not self.case_sensitive:
304
+ normed_value = normed_value.casefold()
305
+
306
+ return normed_value
307
+
308
+ def get_metavar(self, param: Parameter, ctx: Context) -> str | None:
309
+ if param.param_type_name == "option" and not param.show_choices: # type: ignore
310
+ choice_metavars = [
311
+ convert_type(type(choice)).name.upper() for choice in self.choices
312
+ ]
313
+ choices_str = "|".join([*dict.fromkeys(choice_metavars)])
314
+ else:
315
+ choices_str = "|".join(
316
+ [str(i) for i in self._normalized_mapping(ctx=ctx).values()]
317
+ )
318
+
319
+ # Use curly braces to indicate a required argument.
320
+ if param.required and param.param_type_name == "argument":
321
+ return f"{{{choices_str}}}"
322
+
323
+ # Use square braces to indicate an option or optional argument.
324
+ return f"[{choices_str}]"
325
+
326
+ def get_missing_message(self, param: Parameter, ctx: Context | None) -> str:
327
+ """
328
+ Message shown when no choice is passed.
329
+
330
+ .. versionchanged:: 8.2.0 Added ``ctx`` argument.
331
+ """
332
+ return _("Choose from:\n\t{choices}").format(
333
+ choices=",\n\t".join(self._normalized_mapping(ctx=ctx).values())
334
+ )
335
+
336
+ def convert(
337
+ self, value: t.Any, param: Parameter | None, ctx: Context | None
338
+ ) -> ParamTypeValue:
339
+ """
340
+ For a given value from the parser, normalize it and find its
341
+ matching normalized value in the list of choices. Then return the
342
+ matched "original" choice.
343
+ """
344
+ normed_value = self.normalize_choice(choice=value, ctx=ctx)
345
+ normalized_mapping = self._normalized_mapping(ctx=ctx)
346
+
347
+ try:
348
+ return next(
349
+ original
350
+ for original, normalized in normalized_mapping.items()
351
+ if normalized == normed_value
352
+ )
353
+ except StopIteration:
354
+ self.fail(
355
+ self.get_invalid_choice_message(value=value, ctx=ctx),
356
+ param=param,
357
+ ctx=ctx,
358
+ )
359
+
360
+ def get_invalid_choice_message(self, value: t.Any, ctx: Context | None) -> str:
361
+ """Get the error message when the given choice is invalid.
362
+
363
+ :param value: The invalid value.
364
+
365
+ .. versionadded:: 8.2
366
+ """
367
+ choices_str = ", ".join(map(repr, self._normalized_mapping(ctx=ctx).values()))
368
+ return ngettext(
369
+ "{value!r} is not {choice}.",
370
+ "{value!r} is not one of {choices}.",
371
+ len(self.choices),
372
+ ).format(value=value, choice=choices_str, choices=choices_str)
373
+
374
+ def __repr__(self) -> str:
375
+ return f"Choice({list(self.choices)})"
376
+
377
+ def shell_complete(
378
+ self, ctx: Context, param: Parameter, incomplete: str
379
+ ) -> list[CompletionItem]:
380
+ """Complete choices that start with the incomplete value.
381
+
382
+ :param ctx: Invocation context for this command.
383
+ :param param: The parameter that is requesting completion.
384
+ :param incomplete: Value being completed. May be empty.
385
+
386
+ .. versionadded:: 8.0
387
+ """
388
+ from click.shell_completion import CompletionItem
389
+
390
+ str_choices = map(str, self.choices)
391
+
392
+ if self.case_sensitive:
393
+ matched = (c for c in str_choices if c.startswith(incomplete))
394
+ else:
395
+ incomplete = incomplete.lower()
396
+ matched = (c for c in str_choices if c.lower().startswith(incomplete))
397
+
398
+ return [CompletionItem(c) for c in matched]
399
+
400
+
401
+ class DateTime(ParamType):
402
+ """The DateTime type converts date strings into `datetime` objects.
403
+
404
+ The format strings which are checked are configurable, but default to some
405
+ common (non-timezone aware) ISO 8601 formats.
406
+
407
+ When specifying *DateTime* formats, you should only pass a list or a tuple.
408
+ Other iterables, like generators, may lead to surprising results.
409
+
410
+ The format strings are processed using ``datetime.strptime``, and this
411
+ consequently defines the format strings which are allowed.
412
+
413
+ Parsing is tried using each format, in order, and the first format which
414
+ parses successfully is used.
415
+
416
+ :param formats: A list or tuple of date format strings, in the order in
417
+ which they should be tried. Defaults to
418
+ ``'%Y-%m-%d'``, ``'%Y-%m-%dT%H:%M:%S'``,
419
+ ``'%Y-%m-%d %H:%M:%S'``.
420
+ """
421
+
422
+ name = "datetime"
423
+
424
+ def __init__(self, formats: cabc.Sequence[str] | None = None):
425
+ self.formats: cabc.Sequence[str] = formats or [
426
+ "%Y-%m-%d",
427
+ "%Y-%m-%dT%H:%M:%S",
428
+ "%Y-%m-%d %H:%M:%S",
429
+ ]
430
+
431
+ def to_info_dict(self) -> dict[str, t.Any]:
432
+ info_dict = super().to_info_dict()
433
+ info_dict["formats"] = self.formats
434
+ return info_dict
435
+
436
+ def get_metavar(self, param: Parameter, ctx: Context) -> str | None:
437
+ return f"[{'|'.join(self.formats)}]"
438
+
439
+ def _try_to_convert_date(self, value: t.Any, format: str) -> datetime | None:
440
+ try:
441
+ return datetime.strptime(value, format)
442
+ except ValueError:
443
+ return None
444
+
445
+ def convert(
446
+ self, value: t.Any, param: Parameter | None, ctx: Context | None
447
+ ) -> t.Any:
448
+ if isinstance(value, datetime):
449
+ return value
450
+
451
+ for format in self.formats:
452
+ converted = self._try_to_convert_date(value, format)
453
+
454
+ if converted is not None:
455
+ return converted
456
+
457
+ formats_str = ", ".join(map(repr, self.formats))
458
+ self.fail(
459
+ ngettext(
460
+ "{value!r} does not match the format {format}.",
461
+ "{value!r} does not match the formats {formats}.",
462
+ len(self.formats),
463
+ ).format(value=value, format=formats_str, formats=formats_str),
464
+ param,
465
+ ctx,
466
+ )
467
+
468
+ def __repr__(self) -> str:
469
+ return "DateTime"
470
+
471
+
472
+ class _NumberParamTypeBase(ParamType):
473
+ _number_class: t.ClassVar[type[t.Any]]
474
+
475
+ def convert(
476
+ self, value: t.Any, param: Parameter | None, ctx: Context | None
477
+ ) -> t.Any:
478
+ try:
479
+ return self._number_class(value)
480
+ except ValueError:
481
+ self.fail(
482
+ _("{value!r} is not a valid {number_type}.").format(
483
+ value=value, number_type=self.name
484
+ ),
485
+ param,
486
+ ctx,
487
+ )
488
+
489
+
490
+ class _NumberRangeBase(_NumberParamTypeBase):
491
+ def __init__(
492
+ self,
493
+ min: float | None = None,
494
+ max: float | None = None,
495
+ min_open: bool = False,
496
+ max_open: bool = False,
497
+ clamp: bool = False,
498
+ ) -> None:
499
+ self.min = min
500
+ self.max = max
501
+ self.min_open = min_open
502
+ self.max_open = max_open
503
+ self.clamp = clamp
504
+
505
+ def to_info_dict(self) -> dict[str, t.Any]:
506
+ info_dict = super().to_info_dict()
507
+ info_dict.update(
508
+ min=self.min,
509
+ max=self.max,
510
+ min_open=self.min_open,
511
+ max_open=self.max_open,
512
+ clamp=self.clamp,
513
+ )
514
+ return info_dict
515
+
516
+ def convert(
517
+ self, value: t.Any, param: Parameter | None, ctx: Context | None
518
+ ) -> t.Any:
519
+ import operator
520
+
521
+ rv = super().convert(value, param, ctx)
522
+ lt_min: bool = self.min is not None and (
523
+ operator.le if self.min_open else operator.lt
524
+ )(rv, self.min)
525
+ gt_max: bool = self.max is not None and (
526
+ operator.ge if self.max_open else operator.gt
527
+ )(rv, self.max)
528
+
529
+ if self.clamp:
530
+ if lt_min:
531
+ return self._clamp(self.min, 1, self.min_open) # type: ignore
532
+
533
+ if gt_max:
534
+ return self._clamp(self.max, -1, self.max_open) # type: ignore
535
+
536
+ if lt_min or gt_max:
537
+ self.fail(
538
+ _("{value} is not in the range {range}.").format(
539
+ value=rv, range=self._describe_range()
540
+ ),
541
+ param,
542
+ ctx,
543
+ )
544
+
545
+ return rv
546
+
547
+ def _clamp(self, bound: float, dir: t.Literal[1, -1], open: bool) -> float:
548
+ """Find the valid value to clamp to bound in the given
549
+ direction.
550
+
551
+ :param bound: The boundary value.
552
+ :param dir: 1 or -1 indicating the direction to move.
553
+ :param open: If true, the range does not include the bound.
554
+ """
555
+ raise NotImplementedError
556
+
557
+ def _describe_range(self) -> str:
558
+ """Describe the range for use in help text."""
559
+ if self.min is None:
560
+ op = "<" if self.max_open else "<="
561
+ return f"x{op}{self.max}"
562
+
563
+ if self.max is None:
564
+ op = ">" if self.min_open else ">="
565
+ return f"x{op}{self.min}"
566
+
567
+ lop = "<" if self.min_open else "<="
568
+ rop = "<" if self.max_open else "<="
569
+ return f"{self.min}{lop}x{rop}{self.max}"
570
+
571
+ def __repr__(self) -> str:
572
+ clamp = " clamped" if self.clamp else ""
573
+ return f"<{type(self).__name__} {self._describe_range()}{clamp}>"
574
+
575
+
576
+ class IntParamType(_NumberParamTypeBase):
577
+ name = "integer"
578
+ _number_class = int
579
+
580
+ def __repr__(self) -> str:
581
+ return "INT"
582
+
583
+
584
+ class IntRange(_NumberRangeBase, IntParamType):
585
+ """Restrict an :data:`click.INT` value to a range of accepted
586
+ values. See :ref:`ranges`.
587
+
588
+ If ``min`` or ``max`` are not passed, any value is accepted in that
589
+ direction. If ``min_open`` or ``max_open`` are enabled, the
590
+ corresponding boundary is not included in the range.
591
+
592
+ If ``clamp`` is enabled, a value outside the range is clamped to the
593
+ boundary instead of failing.
594
+
595
+ .. versionchanged:: 8.0
596
+ Added the ``min_open`` and ``max_open`` parameters.
597
+ """
598
+
599
+ name = "integer range"
600
+
601
+ def _clamp( # type: ignore
602
+ self, bound: int, dir: t.Literal[1, -1], open: bool
603
+ ) -> int:
604
+ if not open:
605
+ return bound
606
+
607
+ return bound + dir
608
+
609
+
610
+ class FloatParamType(_NumberParamTypeBase):
611
+ name = "float"
612
+ _number_class = float
613
+
614
+ def __repr__(self) -> str:
615
+ return "FLOAT"
616
+
617
+
618
+ class FloatRange(_NumberRangeBase, FloatParamType):
619
+ """Restrict a :data:`click.FLOAT` value to a range of accepted
620
+ values. See :ref:`ranges`.
621
+
622
+ If ``min`` or ``max`` are not passed, any value is accepted in that
623
+ direction. If ``min_open`` or ``max_open`` are enabled, the
624
+ corresponding boundary is not included in the range.
625
+
626
+ If ``clamp`` is enabled, a value outside the range is clamped to the
627
+ boundary instead of failing. This is not supported if either
628
+ boundary is marked ``open``.
629
+
630
+ .. versionchanged:: 8.0
631
+ Added the ``min_open`` and ``max_open`` parameters.
632
+ """
633
+
634
+ name = "float range"
635
+
636
+ def __init__(
637
+ self,
638
+ min: float | None = None,
639
+ max: float | None = None,
640
+ min_open: bool = False,
641
+ max_open: bool = False,
642
+ clamp: bool = False,
643
+ ) -> None:
644
+ super().__init__(
645
+ min=min, max=max, min_open=min_open, max_open=max_open, clamp=clamp
646
+ )
647
+
648
+ if (min_open or max_open) and clamp:
649
+ raise TypeError("Clamping is not supported for open bounds.")
650
+
651
+ def _clamp(self, bound: float, dir: t.Literal[1, -1], open: bool) -> float:
652
+ if not open:
653
+ return bound
654
+
655
+ # Could use math.nextafter here, but clamping an
656
+ # open float range doesn't seem to be particularly useful. It's
657
+ # left up to the user to write a callback to do it if needed.
658
+ raise RuntimeError("Clamping is not supported for open bounds.")
659
+
660
+
661
+ class BoolParamType(ParamType):
662
+ name = "boolean"
663
+
664
+ def convert(
665
+ self, value: t.Any, param: Parameter | None, ctx: Context | None
666
+ ) -> t.Any:
667
+ if value in {False, True}:
668
+ return bool(value)
669
+
670
+ norm = value.strip().lower()
671
+
672
+ if norm in {"1", "true", "t", "yes", "y", "on"}:
673
+ return True
674
+
675
+ if norm in {"0", "false", "f", "no", "n", "off"}:
676
+ return False
677
+
678
+ self.fail(
679
+ _("{value!r} is not a valid boolean.").format(value=value), param, ctx
680
+ )
681
+
682
+ def __repr__(self) -> str:
683
+ return "BOOL"
684
+
685
+
686
+ class UUIDParameterType(ParamType):
687
+ name = "uuid"
688
+
689
+ def convert(
690
+ self, value: t.Any, param: Parameter | None, ctx: Context | None
691
+ ) -> t.Any:
692
+ import uuid
693
+
694
+ if isinstance(value, uuid.UUID):
695
+ return value
696
+
697
+ value = value.strip()
698
+
699
+ try:
700
+ return uuid.UUID(value)
701
+ except ValueError:
702
+ self.fail(
703
+ _("{value!r} is not a valid UUID.").format(value=value), param, ctx
704
+ )
705
+
706
+ def __repr__(self) -> str:
707
+ return "UUID"
708
+
709
+
710
+ class File(ParamType):
711
+ """Declares a parameter to be a file for reading or writing. The file
712
+ is automatically closed once the context tears down (after the command
713
+ finished working).
714
+
715
+ Files can be opened for reading or writing. The special value ``-``
716
+ indicates stdin or stdout depending on the mode.
717
+
718
+ By default, the file is opened for reading text data, but it can also be
719
+ opened in binary mode or for writing. The encoding parameter can be used
720
+ to force a specific encoding.
721
+
722
+ The `lazy` flag controls if the file should be opened immediately or upon
723
+ first IO. The default is to be non-lazy for standard input and output
724
+ streams as well as files opened for reading, `lazy` otherwise. When opening a
725
+ file lazily for reading, it is still opened temporarily for validation, but
726
+ will not be held open until first IO. lazy is mainly useful when opening
727
+ for writing to avoid creating the file until it is needed.
728
+
729
+ Files can also be opened atomically in which case all writes go into a
730
+ separate file in the same folder and upon completion the file will
731
+ be moved over to the original location. This is useful if a file
732
+ regularly read by other users is modified.
733
+
734
+ See :ref:`file-args` for more information.
735
+
736
+ .. versionchanged:: 2.0
737
+ Added the ``atomic`` parameter.
738
+ """
739
+
740
+ name = "filename"
741
+ envvar_list_splitter: t.ClassVar[str] = os.path.pathsep
742
+
743
+ def __init__(
744
+ self,
745
+ mode: str = "r",
746
+ encoding: str | None = None,
747
+ errors: str | None = "strict",
748
+ lazy: bool | None = None,
749
+ atomic: bool = False,
750
+ ) -> None:
751
+ self.mode = mode
752
+ self.encoding = encoding
753
+ self.errors = errors
754
+ self.lazy = lazy
755
+ self.atomic = atomic
756
+
757
+ def to_info_dict(self) -> dict[str, t.Any]:
758
+ info_dict = super().to_info_dict()
759
+ info_dict.update(mode=self.mode, encoding=self.encoding)
760
+ return info_dict
761
+
762
+ def resolve_lazy_flag(self, value: str | os.PathLike[str]) -> bool:
763
+ if self.lazy is not None:
764
+ return self.lazy
765
+ if os.fspath(value) == "-":
766
+ return False
767
+ elif "w" in self.mode:
768
+ return True
769
+ return False
770
+
771
+ def convert(
772
+ self,
773
+ value: str | os.PathLike[str] | t.IO[t.Any],
774
+ param: Parameter | None,
775
+ ctx: Context | None,
776
+ ) -> t.IO[t.Any]:
777
+ if _is_file_like(value):
778
+ return value
779
+
780
+ value = t.cast("str | os.PathLike[str]", value)
781
+
782
+ try:
783
+ lazy = self.resolve_lazy_flag(value)
784
+
785
+ if lazy:
786
+ lf = LazyFile(
787
+ value, self.mode, self.encoding, self.errors, atomic=self.atomic
788
+ )
789
+
790
+ if ctx is not None:
791
+ ctx.call_on_close(lf.close_intelligently)
792
+
793
+ return t.cast("t.IO[t.Any]", lf)
794
+
795
+ f, should_close = open_stream(
796
+ value, self.mode, self.encoding, self.errors, atomic=self.atomic
797
+ )
798
+
799
+ # If a context is provided, we automatically close the file
800
+ # at the end of the context execution (or flush out). If a
801
+ # context does not exist, it's the caller's responsibility to
802
+ # properly close the file. This for instance happens when the
803
+ # type is used with prompts.
804
+ if ctx is not None:
805
+ if should_close:
806
+ ctx.call_on_close(safecall(f.close))
807
+ else:
808
+ ctx.call_on_close(safecall(f.flush))
809
+
810
+ return f
811
+ except OSError as e:
812
+ self.fail(f"'{format_filename(value)}': {e.strerror}", param, ctx)
813
+
814
+ def shell_complete(
815
+ self, ctx: Context, param: Parameter, incomplete: str
816
+ ) -> list[CompletionItem]:
817
+ """Return a special completion marker that tells the completion
818
+ system to use the shell to provide file path completions.
819
+
820
+ :param ctx: Invocation context for this command.
821
+ :param param: The parameter that is requesting completion.
822
+ :param incomplete: Value being completed. May be empty.
823
+
824
+ .. versionadded:: 8.0
825
+ """
826
+ from click.shell_completion import CompletionItem
827
+
828
+ return [CompletionItem(incomplete, type="file")]
829
+
830
+
831
+ def _is_file_like(value: t.Any) -> te.TypeGuard[t.IO[t.Any]]:
832
+ return hasattr(value, "read") or hasattr(value, "write")
833
+
834
+
835
+ class Path(ParamType):
836
+ """The ``Path`` type is similar to the :class:`File` type, but
837
+ returns the filename instead of an open file. Various checks can be
838
+ enabled to validate the type of file and permissions.
839
+
840
+ :param exists: The file or directory needs to exist for the value to
841
+ be valid. If this is not set to ``True``, and the file does not
842
+ exist, then all further checks are silently skipped.
843
+ :param file_okay: Allow a file as a value.
844
+ :param dir_okay: Allow a directory as a value.
845
+ :param readable: if true, a readable check is performed.
846
+ :param writable: if true, a writable check is performed.
847
+ :param executable: if true, an executable check is performed.
848
+ :param resolve_path: Make the value absolute and resolve any
849
+ symlinks. A ``~`` is not expanded, as this is supposed to be
850
+ done by the shell only.
851
+ :param allow_dash: Allow a single dash as a value, which indicates
852
+ a standard stream (but does not open it). Use
853
+ :func:`~click.open_file` to handle opening this value.
854
+ :param path_type: Convert the incoming path value to this type. If
855
+ ``None``, keep Python's default, which is ``str``. Useful to
856
+ convert to :class:`pathlib.Path`.
857
+
858
+ .. versionchanged:: 8.1
859
+ Added the ``executable`` parameter.
860
+
861
+ .. versionchanged:: 8.0
862
+ Allow passing ``path_type=pathlib.Path``.
863
+
864
+ .. versionchanged:: 6.0
865
+ Added the ``allow_dash`` parameter.
866
+ """
867
+
868
+ envvar_list_splitter: t.ClassVar[str] = os.path.pathsep
869
+
870
+ def __init__(
871
+ self,
872
+ exists: bool = False,
873
+ file_okay: bool = True,
874
+ dir_okay: bool = True,
875
+ writable: bool = False,
876
+ readable: bool = True,
877
+ resolve_path: bool = False,
878
+ allow_dash: bool = False,
879
+ path_type: type[t.Any] | None = None,
880
+ executable: bool = False,
881
+ ):
882
+ self.exists = exists
883
+ self.file_okay = file_okay
884
+ self.dir_okay = dir_okay
885
+ self.readable = readable
886
+ self.writable = writable
887
+ self.executable = executable
888
+ self.resolve_path = resolve_path
889
+ self.allow_dash = allow_dash
890
+ self.type = path_type
891
+
892
+ if self.file_okay and not self.dir_okay:
893
+ self.name: str = _("file")
894
+ elif self.dir_okay and not self.file_okay:
895
+ self.name = _("directory")
896
+ else:
897
+ self.name = _("path")
898
+
899
+ def to_info_dict(self) -> dict[str, t.Any]:
900
+ info_dict = super().to_info_dict()
901
+ info_dict.update(
902
+ exists=self.exists,
903
+ file_okay=self.file_okay,
904
+ dir_okay=self.dir_okay,
905
+ writable=self.writable,
906
+ readable=self.readable,
907
+ allow_dash=self.allow_dash,
908
+ )
909
+ return info_dict
910
+
911
+ def coerce_path_result(
912
+ self, value: str | os.PathLike[str]
913
+ ) -> str | bytes | os.PathLike[str]:
914
+ if self.type is not None and not isinstance(value, self.type):
915
+ if self.type is str:
916
+ return os.fsdecode(value)
917
+ elif self.type is bytes:
918
+ return os.fsencode(value)
919
+ else:
920
+ return t.cast("os.PathLike[str]", self.type(value))
921
+
922
+ return value
923
+
924
+ def convert(
925
+ self,
926
+ value: str | os.PathLike[str],
927
+ param: Parameter | None,
928
+ ctx: Context | None,
929
+ ) -> str | bytes | os.PathLike[str]:
930
+ rv = value
931
+
932
+ is_dash = self.file_okay and self.allow_dash and rv in (b"-", "-")
933
+
934
+ if not is_dash:
935
+ if self.resolve_path:
936
+ rv = os.path.realpath(rv)
937
+
938
+ try:
939
+ st = os.stat(rv)
940
+ except OSError:
941
+ if not self.exists:
942
+ return self.coerce_path_result(rv)
943
+ self.fail(
944
+ _("{name} {filename!r} does not exist.").format(
945
+ name=self.name.title(), filename=format_filename(value)
946
+ ),
947
+ param,
948
+ ctx,
949
+ )
950
+
951
+ if not self.file_okay and stat.S_ISREG(st.st_mode):
952
+ self.fail(
953
+ _("{name} {filename!r} is a file.").format(
954
+ name=self.name.title(), filename=format_filename(value)
955
+ ),
956
+ param,
957
+ ctx,
958
+ )
959
+ if not self.dir_okay and stat.S_ISDIR(st.st_mode):
960
+ self.fail(
961
+ _("{name} {filename!r} is a directory.").format(
962
+ name=self.name.title(), filename=format_filename(value)
963
+ ),
964
+ param,
965
+ ctx,
966
+ )
967
+
968
+ if self.readable and not os.access(rv, os.R_OK):
969
+ self.fail(
970
+ _("{name} {filename!r} is not readable.").format(
971
+ name=self.name.title(), filename=format_filename(value)
972
+ ),
973
+ param,
974
+ ctx,
975
+ )
976
+
977
+ if self.writable and not os.access(rv, os.W_OK):
978
+ self.fail(
979
+ _("{name} {filename!r} is not writable.").format(
980
+ name=self.name.title(), filename=format_filename(value)
981
+ ),
982
+ param,
983
+ ctx,
984
+ )
985
+
986
+ if self.executable and not os.access(value, os.X_OK):
987
+ self.fail(
988
+ _("{name} {filename!r} is not executable.").format(
989
+ name=self.name.title(), filename=format_filename(value)
990
+ ),
991
+ param,
992
+ ctx,
993
+ )
994
+
995
+ return self.coerce_path_result(rv)
996
+
997
+ def shell_complete(
998
+ self, ctx: Context, param: Parameter, incomplete: str
999
+ ) -> list[CompletionItem]:
1000
+ """Return a special completion marker that tells the completion
1001
+ system to use the shell to provide path completions for only
1002
+ directories or any paths.
1003
+
1004
+ :param ctx: Invocation context for this command.
1005
+ :param param: The parameter that is requesting completion.
1006
+ :param incomplete: Value being completed. May be empty.
1007
+
1008
+ .. versionadded:: 8.0
1009
+ """
1010
+ from click.shell_completion import CompletionItem
1011
+
1012
+ type = "dir" if self.dir_okay and not self.file_okay else "file"
1013
+ return [CompletionItem(incomplete, type=type)]
1014
+
1015
+
1016
+ class Tuple(CompositeParamType):
1017
+ """The default behavior of Click is to apply a type on a value directly.
1018
+ This works well in most cases, except for when `nargs` is set to a fixed
1019
+ count and different types should be used for different items. In this
1020
+ case the :class:`Tuple` type can be used. This type can only be used
1021
+ if `nargs` is set to a fixed number.
1022
+
1023
+ For more information see :ref:`tuple-type`.
1024
+
1025
+ This can be selected by using a Python tuple literal as a type.
1026
+
1027
+ :param types: a list of types that should be used for the tuple items.
1028
+ """
1029
+
1030
+ def __init__(self, types: cabc.Sequence[type[t.Any] | ParamType]) -> None:
1031
+ self.types: cabc.Sequence[ParamType] = [convert_type(ty) for ty in types]
1032
+
1033
+ def to_info_dict(self) -> dict[str, t.Any]:
1034
+ info_dict = super().to_info_dict()
1035
+ info_dict["types"] = [t.to_info_dict() for t in self.types]
1036
+ return info_dict
1037
+
1038
+ @property
1039
+ def name(self) -> str: # type: ignore
1040
+ return f"<{' '.join(ty.name for ty in self.types)}>"
1041
+
1042
+ @property
1043
+ def arity(self) -> int: # type: ignore
1044
+ return len(self.types)
1045
+
1046
+ def convert(
1047
+ self, value: t.Any, param: Parameter | None, ctx: Context | None
1048
+ ) -> t.Any:
1049
+ len_type = len(self.types)
1050
+ len_value = len(value)
1051
+
1052
+ if len_value != len_type:
1053
+ self.fail(
1054
+ ngettext(
1055
+ "{len_type} values are required, but {len_value} was given.",
1056
+ "{len_type} values are required, but {len_value} were given.",
1057
+ len_value,
1058
+ ).format(len_type=len_type, len_value=len_value),
1059
+ param=param,
1060
+ ctx=ctx,
1061
+ )
1062
+
1063
+ return tuple(
1064
+ ty(x, param, ctx) for ty, x in zip(self.types, value, strict=False)
1065
+ )
1066
+
1067
+
1068
+ def convert_type(ty: t.Any | None, default: t.Any | None = None) -> ParamType:
1069
+ """Find the most appropriate :class:`ParamType` for the given Python
1070
+ type. If the type isn't provided, it can be inferred from a default
1071
+ value.
1072
+ """
1073
+ guessed_type = False
1074
+
1075
+ if ty is None and default is not None:
1076
+ if isinstance(default, (tuple, list)):
1077
+ # If the default is empty, ty will remain None and will
1078
+ # return STRING.
1079
+ if default:
1080
+ item = default[0]
1081
+
1082
+ # A tuple of tuples needs to detect the inner types.
1083
+ # Can't call convert recursively because that would
1084
+ # incorrectly unwind the tuple to a single type.
1085
+ if isinstance(item, (tuple, list)):
1086
+ ty = tuple(map(type, item))
1087
+ else:
1088
+ ty = type(item)
1089
+ else:
1090
+ ty = type(default)
1091
+
1092
+ guessed_type = True
1093
+
1094
+ if isinstance(ty, tuple):
1095
+ return Tuple(ty)
1096
+
1097
+ if isinstance(ty, ParamType):
1098
+ return ty
1099
+
1100
+ if ty is str or ty is None:
1101
+ return STRING
1102
+
1103
+ if ty is int:
1104
+ return INT
1105
+
1106
+ if ty is float:
1107
+ return FLOAT
1108
+
1109
+ if ty is bool:
1110
+ return BOOL
1111
+
1112
+ if guessed_type:
1113
+ return STRING
1114
+
1115
+ if __debug__:
1116
+ try:
1117
+ if issubclass(ty, ParamType):
1118
+ raise AssertionError(
1119
+ f"Attempted to use an uninstantiated parameter type ({ty})."
1120
+ )
1121
+ except TypeError:
1122
+ # ty is an instance (correct), so issubclass fails.
1123
+ pass
1124
+
1125
+ return FuncParamType(ty)
1126
+
1127
+
1128
+ #: A dummy parameter type that just does nothing. From a user's
1129
+ #: perspective this appears to just be the same as `STRING` but
1130
+ #: internally no string conversion takes place if the input was bytes.
1131
+ #: This is usually useful when working with file paths as they can
1132
+ #: appear in bytes and unicode.
1133
+ #:
1134
+ #: For path related uses the :class:`Path` type is a better choice but
1135
+ #: there are situations where an unprocessed type is useful which is why
1136
+ #: it is is provided.
1137
+ #:
1138
+ #: .. versionadded:: 4.0
1139
+ UNPROCESSED = UnprocessedParamType()
1140
+
1141
+ #: A unicode string parameter type which is the implicit default. This
1142
+ #: can also be selected by using ``str`` as type.
1143
+ STRING = StringParamType()
1144
+
1145
+ #: An integer parameter. This can also be selected by using ``int`` as
1146
+ #: type.
1147
+ INT = IntParamType()
1148
+
1149
+ #: A floating point value parameter. This can also be selected by using
1150
+ #: ``float`` as type.
1151
+ FLOAT = FloatParamType()
1152
+
1153
+ #: A boolean parameter. This is the default for boolean flags. This can
1154
+ #: also be selected by using ``bool`` as a type.
1155
+ BOOL = BoolParamType()
1156
+
1157
+ #: A UUID parameter.
1158
+ UUID = UUIDParameterType()
1159
+
1160
+
1161
+ class OptionHelpExtra(t.TypedDict, total=False):
1162
+ envvars: tuple[str, ...]
1163
+ default: str
1164
+ range: str
1165
+ required: str
lib/python3.12/site-packages/click/utils.py ADDED
@@ -0,0 +1,627 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import collections.abc as cabc
4
+ import os
5
+ import re
6
+ import sys
7
+ import typing as t
8
+ from functools import update_wrapper
9
+ from types import ModuleType
10
+ from types import TracebackType
11
+
12
+ from ._compat import _default_text_stderr
13
+ from ._compat import _default_text_stdout
14
+ from ._compat import _find_binary_writer
15
+ from ._compat import auto_wrap_for_ansi
16
+ from ._compat import binary_streams
17
+ from ._compat import open_stream
18
+ from ._compat import should_strip_ansi
19
+ from ._compat import strip_ansi
20
+ from ._compat import text_streams
21
+ from ._compat import WIN
22
+ from .globals import resolve_color_default
23
+
24
+ if t.TYPE_CHECKING:
25
+ import typing_extensions as te
26
+
27
+ P = te.ParamSpec("P")
28
+
29
+ R = t.TypeVar("R")
30
+
31
+
32
+ def _posixify(name: str) -> str:
33
+ return "-".join(name.split()).lower()
34
+
35
+
36
+ def safecall(func: t.Callable[P, R]) -> t.Callable[P, R | None]:
37
+ """Wraps a function so that it swallows exceptions."""
38
+
39
+ def wrapper(*args: P.args, **kwargs: P.kwargs) -> R | None:
40
+ try:
41
+ return func(*args, **kwargs)
42
+ except Exception:
43
+ pass
44
+ return None
45
+
46
+ return update_wrapper(wrapper, func)
47
+
48
+
49
+ def make_str(value: t.Any) -> str:
50
+ """Converts a value into a valid string."""
51
+ if isinstance(value, bytes):
52
+ try:
53
+ return value.decode(sys.getfilesystemencoding())
54
+ except UnicodeError:
55
+ return value.decode("utf-8", "replace")
56
+ return str(value)
57
+
58
+
59
+ def make_default_short_help(help: str, max_length: int = 45) -> str:
60
+ """Returns a condensed version of help string."""
61
+ # Consider only the first paragraph.
62
+ paragraph_end = help.find("\n\n")
63
+
64
+ if paragraph_end != -1:
65
+ help = help[:paragraph_end]
66
+
67
+ # Collapse newlines, tabs, and spaces.
68
+ words = help.split()
69
+
70
+ if not words:
71
+ return ""
72
+
73
+ # The first paragraph started with a "no rewrap" marker, ignore it.
74
+ if words[0] == "\b":
75
+ words = words[1:]
76
+
77
+ total_length = 0
78
+ last_index = len(words) - 1
79
+
80
+ for i, word in enumerate(words):
81
+ total_length += len(word) + (i > 0)
82
+
83
+ if total_length > max_length: # too long, truncate
84
+ break
85
+
86
+ if word[-1] == ".": # sentence end, truncate without "..."
87
+ return " ".join(words[: i + 1])
88
+
89
+ if total_length == max_length and i != last_index:
90
+ break # not at sentence end, truncate with "..."
91
+ else:
92
+ return " ".join(words) # no truncation needed
93
+
94
+ # Account for the length of the suffix.
95
+ total_length += len("...")
96
+
97
+ # remove words until the length is short enough
98
+ while i > 0:
99
+ total_length -= len(words[i]) + (i > 0)
100
+
101
+ if total_length <= max_length:
102
+ break
103
+
104
+ i -= 1
105
+
106
+ return " ".join(words[:i]) + "..."
107
+
108
+
109
+ class LazyFile:
110
+ """A lazy file works like a regular file but it does not fully open
111
+ the file but it does perform some basic checks early to see if the
112
+ filename parameter does make sense. This is useful for safely opening
113
+ files for writing.
114
+ """
115
+
116
+ def __init__(
117
+ self,
118
+ filename: str | os.PathLike[str],
119
+ mode: str = "r",
120
+ encoding: str | None = None,
121
+ errors: str | None = "strict",
122
+ atomic: bool = False,
123
+ ):
124
+ self.name: str = os.fspath(filename)
125
+ self.mode = mode
126
+ self.encoding = encoding
127
+ self.errors = errors
128
+ self.atomic = atomic
129
+ self._f: t.IO[t.Any] | None
130
+ self.should_close: bool
131
+
132
+ if self.name == "-":
133
+ self._f, self.should_close = open_stream(filename, mode, encoding, errors)
134
+ else:
135
+ if "r" in mode:
136
+ # Open and close the file in case we're opening it for
137
+ # reading so that we can catch at least some errors in
138
+ # some cases early.
139
+ open(filename, mode).close()
140
+ self._f = None
141
+ self.should_close = True
142
+
143
+ def __getattr__(self, name: str) -> t.Any:
144
+ return getattr(self.open(), name)
145
+
146
+ def __repr__(self) -> str:
147
+ if self._f is not None:
148
+ return repr(self._f)
149
+ return f"<unopened file '{format_filename(self.name)}' {self.mode}>"
150
+
151
+ def open(self) -> t.IO[t.Any]:
152
+ """Opens the file if it's not yet open. This call might fail with
153
+ a :exc:`FileError`. Not handling this error will produce an error
154
+ that Click shows.
155
+ """
156
+ if self._f is not None:
157
+ return self._f
158
+ try:
159
+ rv, self.should_close = open_stream(
160
+ self.name, self.mode, self.encoding, self.errors, atomic=self.atomic
161
+ )
162
+ except OSError as e:
163
+ from .exceptions import FileError
164
+
165
+ raise FileError(self.name, hint=e.strerror) from e
166
+ self._f = rv
167
+ return rv
168
+
169
+ def close(self) -> None:
170
+ """Closes the underlying file, no matter what."""
171
+ if self._f is not None:
172
+ self._f.close()
173
+
174
+ def close_intelligently(self) -> None:
175
+ """This function only closes the file if it was opened by the lazy
176
+ file wrapper. For instance this will never close stdin.
177
+ """
178
+ if self.should_close:
179
+ self.close()
180
+
181
+ def __enter__(self) -> LazyFile:
182
+ return self
183
+
184
+ def __exit__(
185
+ self,
186
+ exc_type: type[BaseException] | None,
187
+ exc_value: BaseException | None,
188
+ tb: TracebackType | None,
189
+ ) -> None:
190
+ self.close_intelligently()
191
+
192
+ def __iter__(self) -> cabc.Iterator[t.AnyStr]:
193
+ self.open()
194
+ return iter(self._f) # type: ignore
195
+
196
+
197
+ class KeepOpenFile:
198
+ def __init__(self, file: t.IO[t.Any]) -> None:
199
+ self._file: t.IO[t.Any] = file
200
+
201
+ def __getattr__(self, name: str) -> t.Any:
202
+ return getattr(self._file, name)
203
+
204
+ def __enter__(self) -> KeepOpenFile:
205
+ return self
206
+
207
+ def __exit__(
208
+ self,
209
+ exc_type: type[BaseException] | None,
210
+ exc_value: BaseException | None,
211
+ tb: TracebackType | None,
212
+ ) -> None:
213
+ pass
214
+
215
+ def __repr__(self) -> str:
216
+ return repr(self._file)
217
+
218
+ def __iter__(self) -> cabc.Iterator[t.AnyStr]:
219
+ return iter(self._file)
220
+
221
+
222
+ def echo(
223
+ message: t.Any | None = None,
224
+ file: t.IO[t.Any] | None = None,
225
+ nl: bool = True,
226
+ err: bool = False,
227
+ color: bool | None = None,
228
+ ) -> None:
229
+ """Print a message and newline to stdout or a file. This should be
230
+ used instead of :func:`print` because it provides better support
231
+ for different data, files, and environments.
232
+
233
+ Compared to :func:`print`, this does the following:
234
+
235
+ - Ensures that the output encoding is not misconfigured on Linux.
236
+ - Supports Unicode in the Windows console.
237
+ - Supports writing to binary outputs, and supports writing bytes
238
+ to text outputs.
239
+ - Supports colors and styles on Windows.
240
+ - Removes ANSI color and style codes if the output does not look
241
+ like an interactive terminal.
242
+ - Always flushes the output.
243
+
244
+ :param message: The string or bytes to output. Other objects are
245
+ converted to strings.
246
+ :param file: The file to write to. Defaults to ``stdout``.
247
+ :param err: Write to ``stderr`` instead of ``stdout``.
248
+ :param nl: Print a newline after the message. Enabled by default.
249
+ :param color: Force showing or hiding colors and other styles. By
250
+ default Click will remove color if the output does not look like
251
+ an interactive terminal.
252
+
253
+ .. versionchanged:: 6.0
254
+ Support Unicode output on the Windows console. Click does not
255
+ modify ``sys.stdout``, so ``sys.stdout.write()`` and ``print()``
256
+ will still not support Unicode.
257
+
258
+ .. versionchanged:: 4.0
259
+ Added the ``color`` parameter.
260
+
261
+ .. versionadded:: 3.0
262
+ Added the ``err`` parameter.
263
+
264
+ .. versionchanged:: 2.0
265
+ Support colors on Windows if colorama is installed.
266
+ """
267
+ if file is None:
268
+ if err:
269
+ file = _default_text_stderr()
270
+ else:
271
+ file = _default_text_stdout()
272
+
273
+ # There are no standard streams attached to write to. For example,
274
+ # pythonw on Windows.
275
+ if file is None:
276
+ return
277
+
278
+ # Convert non bytes/text into the native string type.
279
+ if message is not None and not isinstance(message, (str, bytes, bytearray)):
280
+ out: str | bytes | None = str(message)
281
+ else:
282
+ out = message
283
+
284
+ if nl:
285
+ out = out or ""
286
+ if isinstance(out, str):
287
+ out += "\n"
288
+ else:
289
+ out += b"\n"
290
+
291
+ if not out:
292
+ file.flush()
293
+ return
294
+
295
+ # If there is a message and the value looks like bytes, we manually
296
+ # need to find the binary stream and write the message in there.
297
+ # This is done separately so that most stream types will work as you
298
+ # would expect. Eg: you can write to StringIO for other cases.
299
+ if isinstance(out, (bytes, bytearray)):
300
+ binary_file = _find_binary_writer(file)
301
+
302
+ if binary_file is not None:
303
+ file.flush()
304
+ binary_file.write(out)
305
+ binary_file.flush()
306
+ return
307
+
308
+ # ANSI style code support. For no message or bytes, nothing happens.
309
+ # When outputting to a file instead of a terminal, strip codes.
310
+ else:
311
+ color = resolve_color_default(color)
312
+
313
+ if should_strip_ansi(file, color):
314
+ out = strip_ansi(out)
315
+ elif WIN:
316
+ if auto_wrap_for_ansi is not None:
317
+ file = auto_wrap_for_ansi(file, color) # type: ignore
318
+ elif not color:
319
+ out = strip_ansi(out)
320
+
321
+ file.write(out) # type: ignore
322
+ file.flush()
323
+
324
+
325
+ def get_binary_stream(name: t.Literal["stdin", "stdout", "stderr"]) -> t.BinaryIO:
326
+ """Returns a system stream for byte processing.
327
+
328
+ :param name: the name of the stream to open. Valid names are ``'stdin'``,
329
+ ``'stdout'`` and ``'stderr'``
330
+ """
331
+ opener = binary_streams.get(name)
332
+ if opener is None:
333
+ raise TypeError(f"Unknown standard stream '{name}'")
334
+ return opener()
335
+
336
+
337
+ def get_text_stream(
338
+ name: t.Literal["stdin", "stdout", "stderr"],
339
+ encoding: str | None = None,
340
+ errors: str | None = "strict",
341
+ ) -> t.TextIO:
342
+ """Returns a system stream for text processing. This usually returns
343
+ a wrapped stream around a binary stream returned from
344
+ :func:`get_binary_stream` but it also can take shortcuts for already
345
+ correctly configured streams.
346
+
347
+ :param name: the name of the stream to open. Valid names are ``'stdin'``,
348
+ ``'stdout'`` and ``'stderr'``
349
+ :param encoding: overrides the detected default encoding.
350
+ :param errors: overrides the default error mode.
351
+ """
352
+ opener = text_streams.get(name)
353
+ if opener is None:
354
+ raise TypeError(f"Unknown standard stream '{name}'")
355
+ return opener(encoding, errors)
356
+
357
+
358
+ def open_file(
359
+ filename: str | os.PathLike[str],
360
+ mode: str = "r",
361
+ encoding: str | None = None,
362
+ errors: str | None = "strict",
363
+ lazy: bool = False,
364
+ atomic: bool = False,
365
+ ) -> t.IO[t.Any]:
366
+ """Open a file, with extra behavior to handle ``'-'`` to indicate
367
+ a standard stream, lazy open on write, and atomic write. Similar to
368
+ the behavior of the :class:`~click.File` param type.
369
+
370
+ If ``'-'`` is given to open ``stdout`` or ``stdin``, the stream is
371
+ wrapped so that using it in a context manager will not close it.
372
+ This makes it possible to use the function without accidentally
373
+ closing a standard stream:
374
+
375
+ .. code-block:: python
376
+
377
+ with open_file(filename) as f:
378
+ ...
379
+
380
+ :param filename: The name or Path of the file to open, or ``'-'`` for
381
+ ``stdin``/``stdout``.
382
+ :param mode: The mode in which to open the file.
383
+ :param encoding: The encoding to decode or encode a file opened in
384
+ text mode.
385
+ :param errors: The error handling mode.
386
+ :param lazy: Wait to open the file until it is accessed. For read
387
+ mode, the file is temporarily opened to raise access errors
388
+ early, then closed until it is read again.
389
+ :param atomic: Write to a temporary file and replace the given file
390
+ on close.
391
+
392
+ .. versionadded:: 3.0
393
+ """
394
+ if lazy:
395
+ return t.cast(
396
+ "t.IO[t.Any]", LazyFile(filename, mode, encoding, errors, atomic=atomic)
397
+ )
398
+
399
+ f, should_close = open_stream(filename, mode, encoding, errors, atomic=atomic)
400
+
401
+ if not should_close:
402
+ f = t.cast("t.IO[t.Any]", KeepOpenFile(f))
403
+
404
+ return f
405
+
406
+
407
+ def format_filename(
408
+ filename: str | bytes | os.PathLike[str] | os.PathLike[bytes],
409
+ shorten: bool = False,
410
+ ) -> str:
411
+ """Format a filename as a string for display. Ensures the filename can be
412
+ displayed by replacing any invalid bytes or surrogate escapes in the name
413
+ with the replacement character ``�``.
414
+
415
+ Invalid bytes or surrogate escapes will raise an error when written to a
416
+ stream with ``errors="strict"``. This will typically happen with ``stdout``
417
+ when the locale is something like ``en_GB.UTF-8``.
418
+
419
+ Many scenarios *are* safe to write surrogates though, due to PEP 538 and
420
+ PEP 540, including:
421
+
422
+ - Writing to ``stderr``, which uses ``errors="backslashreplace"``.
423
+ - The system has ``LANG=C.UTF-8``, ``C``, or ``POSIX``. Python opens
424
+ stdout and stderr with ``errors="surrogateescape"``.
425
+ - None of ``LANG/LC_*`` are set. Python assumes ``LANG=C.UTF-8``.
426
+ - Python is started in UTF-8 mode with ``PYTHONUTF8=1`` or ``-X utf8``.
427
+ Python opens stdout and stderr with ``errors="surrogateescape"``.
428
+
429
+ :param filename: formats a filename for UI display. This will also convert
430
+ the filename into unicode without failing.
431
+ :param shorten: this optionally shortens the filename to strip of the
432
+ path that leads up to it.
433
+ """
434
+ if shorten:
435
+ filename = os.path.basename(filename)
436
+ else:
437
+ filename = os.fspath(filename)
438
+
439
+ if isinstance(filename, bytes):
440
+ filename = filename.decode(sys.getfilesystemencoding(), "replace")
441
+ else:
442
+ filename = filename.encode("utf-8", "surrogateescape").decode(
443
+ "utf-8", "replace"
444
+ )
445
+
446
+ return filename
447
+
448
+
449
+ def get_app_dir(app_name: str, roaming: bool = True, force_posix: bool = False) -> str:
450
+ r"""Returns the config folder for the application. The default behavior
451
+ is to return whatever is most appropriate for the operating system.
452
+
453
+ To give you an idea, for an app called ``"Foo Bar"``, something like
454
+ the following folders could be returned:
455
+
456
+ Mac OS X:
457
+ ``~/Library/Application Support/Foo Bar``
458
+ Mac OS X (POSIX):
459
+ ``~/.foo-bar``
460
+ Unix:
461
+ ``~/.config/foo-bar``
462
+ Unix (POSIX):
463
+ ``~/.foo-bar``
464
+ Windows (roaming):
465
+ ``C:\Users\<user>\AppData\Roaming\Foo Bar``
466
+ Windows (not roaming):
467
+ ``C:\Users\<user>\AppData\Local\Foo Bar``
468
+
469
+ .. versionadded:: 2.0
470
+
471
+ :param app_name: the application name. This should be properly capitalized
472
+ and can contain whitespace.
473
+ :param roaming: controls if the folder should be roaming or not on Windows.
474
+ Has no effect otherwise.
475
+ :param force_posix: if this is set to `True` then on any POSIX system the
476
+ folder will be stored in the home folder with a leading
477
+ dot instead of the XDG config home or darwin's
478
+ application support folder.
479
+ """
480
+ if WIN:
481
+ key = "APPDATA" if roaming else "LOCALAPPDATA"
482
+ folder = os.environ.get(key)
483
+ if folder is None:
484
+ folder = os.path.expanduser("~")
485
+ return os.path.join(folder, app_name)
486
+ if force_posix:
487
+ return os.path.join(os.path.expanduser(f"~/.{_posixify(app_name)}"))
488
+ if sys.platform == "darwin":
489
+ return os.path.join(
490
+ os.path.expanduser("~/Library/Application Support"), app_name
491
+ )
492
+ return os.path.join(
493
+ os.environ.get("XDG_CONFIG_HOME", os.path.expanduser("~/.config")),
494
+ _posixify(app_name),
495
+ )
496
+
497
+
498
+ class PacifyFlushWrapper:
499
+ """This wrapper is used to catch and suppress BrokenPipeErrors resulting
500
+ from ``.flush()`` being called on broken pipe during the shutdown/final-GC
501
+ of the Python interpreter. Notably ``.flush()`` is always called on
502
+ ``sys.stdout`` and ``sys.stderr``. So as to have minimal impact on any
503
+ other cleanup code, and the case where the underlying file is not a broken
504
+ pipe, all calls and attributes are proxied.
505
+ """
506
+
507
+ def __init__(self, wrapped: t.IO[t.Any]) -> None:
508
+ self.wrapped = wrapped
509
+
510
+ def flush(self) -> None:
511
+ try:
512
+ self.wrapped.flush()
513
+ except OSError as e:
514
+ import errno
515
+
516
+ if e.errno != errno.EPIPE:
517
+ raise
518
+
519
+ def __getattr__(self, attr: str) -> t.Any:
520
+ return getattr(self.wrapped, attr)
521
+
522
+
523
+ def _detect_program_name(
524
+ path: str | None = None, _main: ModuleType | None = None
525
+ ) -> str:
526
+ """Determine the command used to run the program, for use in help
527
+ text. If a file or entry point was executed, the file name is
528
+ returned. If ``python -m`` was used to execute a module or package,
529
+ ``python -m name`` is returned.
530
+
531
+ This doesn't try to be too precise, the goal is to give a concise
532
+ name for help text. Files are only shown as their name without the
533
+ path. ``python`` is only shown for modules, and the full path to
534
+ ``sys.executable`` is not shown.
535
+
536
+ :param path: The Python file being executed. Python puts this in
537
+ ``sys.argv[0]``, which is used by default.
538
+ :param _main: The ``__main__`` module. This should only be passed
539
+ during internal testing.
540
+
541
+ .. versionadded:: 8.0
542
+ Based on command args detection in the Werkzeug reloader.
543
+
544
+ :meta private:
545
+ """
546
+ if _main is None:
547
+ _main = sys.modules["__main__"]
548
+
549
+ if not path:
550
+ path = sys.argv[0]
551
+
552
+ # The value of __package__ indicates how Python was called. It may
553
+ # not exist if a setuptools script is installed as an egg. It may be
554
+ # set incorrectly for entry points created with pip on Windows.
555
+ # It is set to "" inside a Shiv or PEX zipapp.
556
+ if getattr(_main, "__package__", None) in {None, ""} or (
557
+ os.name == "nt"
558
+ and _main.__package__ == ""
559
+ and not os.path.exists(path)
560
+ and os.path.exists(f"{path}.exe")
561
+ ):
562
+ # Executed a file, like "python app.py".
563
+ return os.path.basename(path)
564
+
565
+ # Executed a module, like "python -m example".
566
+ # Rewritten by Python from "-m script" to "/path/to/script.py".
567
+ # Need to look at main module to determine how it was executed.
568
+ py_module = t.cast(str, _main.__package__)
569
+ name = os.path.splitext(os.path.basename(path))[0]
570
+
571
+ # A submodule like "example.cli".
572
+ if name != "__main__":
573
+ py_module = f"{py_module}.{name}"
574
+
575
+ return f"python -m {py_module.lstrip('.')}"
576
+
577
+
578
+ def _expand_args(
579
+ args: cabc.Iterable[str],
580
+ *,
581
+ user: bool = True,
582
+ env: bool = True,
583
+ glob_recursive: bool = True,
584
+ ) -> list[str]:
585
+ """Simulate Unix shell expansion with Python functions.
586
+
587
+ See :func:`glob.glob`, :func:`os.path.expanduser`, and
588
+ :func:`os.path.expandvars`.
589
+
590
+ This is intended for use on Windows, where the shell does not do any
591
+ expansion. It may not exactly match what a Unix shell would do.
592
+
593
+ :param args: List of command line arguments to expand.
594
+ :param user: Expand user home directory.
595
+ :param env: Expand environment variables.
596
+ :param glob_recursive: ``**`` matches directories recursively.
597
+
598
+ .. versionchanged:: 8.1
599
+ Invalid glob patterns are treated as empty expansions rather
600
+ than raising an error.
601
+
602
+ .. versionadded:: 8.0
603
+
604
+ :meta private:
605
+ """
606
+ from glob import glob
607
+
608
+ out = []
609
+
610
+ for arg in args:
611
+ if user:
612
+ arg = os.path.expanduser(arg)
613
+
614
+ if env:
615
+ arg = os.path.expandvars(arg)
616
+
617
+ try:
618
+ matches = glob(arg, recursive=glob_recursive)
619
+ except re.error:
620
+ matches = []
621
+
622
+ if not matches:
623
+ out.append(arg)
624
+ else:
625
+ out.extend(matches)
626
+
627
+ return out
lib/python3.12/site-packages/contourpy/__init__.py ADDED
@@ -0,0 +1,285 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING
4
+
5
+ import numpy as np
6
+
7
+ from contourpy._contourpy import (
8
+ ContourGenerator,
9
+ FillType,
10
+ LineType,
11
+ Mpl2005ContourGenerator,
12
+ Mpl2014ContourGenerator,
13
+ SerialContourGenerator,
14
+ ThreadedContourGenerator,
15
+ ZInterp,
16
+ max_threads,
17
+ )
18
+ from contourpy._version import __version__
19
+ from contourpy.chunk import calc_chunk_sizes
20
+ from contourpy.convert import (
21
+ convert_filled,
22
+ convert_lines,
23
+ convert_multi_filled,
24
+ convert_multi_lines,
25
+ )
26
+ from contourpy.dechunk import (
27
+ dechunk_filled,
28
+ dechunk_lines,
29
+ dechunk_multi_filled,
30
+ dechunk_multi_lines,
31
+ )
32
+ from contourpy.enum_util import as_fill_type, as_line_type, as_z_interp
33
+
34
+ if TYPE_CHECKING:
35
+ from typing import Any
36
+
37
+ from numpy.typing import ArrayLike
38
+
39
+ from ._contourpy import CoordinateArray, MaskArray
40
+
41
+ __all__ = [
42
+ "__version__",
43
+ "contour_generator",
44
+ "convert_filled",
45
+ "convert_lines",
46
+ "convert_multi_filled",
47
+ "convert_multi_lines",
48
+ "dechunk_filled",
49
+ "dechunk_lines",
50
+ "dechunk_multi_filled",
51
+ "dechunk_multi_lines",
52
+ "max_threads",
53
+ "FillType",
54
+ "LineType",
55
+ "ContourGenerator",
56
+ "Mpl2005ContourGenerator",
57
+ "Mpl2014ContourGenerator",
58
+ "SerialContourGenerator",
59
+ "ThreadedContourGenerator",
60
+ "ZInterp",
61
+ ]
62
+
63
+
64
+ # Simple mapping of algorithm name to class name.
65
+ _class_lookup: dict[str, type[ContourGenerator]] = {
66
+ "mpl2005": Mpl2005ContourGenerator,
67
+ "mpl2014": Mpl2014ContourGenerator,
68
+ "serial": SerialContourGenerator,
69
+ "threaded": ThreadedContourGenerator,
70
+ }
71
+
72
+
73
+ def _remove_z_mask(
74
+ z: ArrayLike | np.ma.MaskedArray[Any, Any] | None,
75
+ ) -> tuple[CoordinateArray, MaskArray | None]:
76
+ # Preserve mask if present.
77
+ z_array = np.ma.asarray(z, dtype=np.float64) # type: ignore[no-untyped-call]
78
+ z_masked = np.ma.masked_invalid(z_array, copy=False) # type: ignore[no-untyped-call]
79
+
80
+ if np.ma.is_masked(z_masked):
81
+ mask = np.ma.getmask(z_masked)
82
+ else:
83
+ mask = None
84
+
85
+ return np.ma.getdata(z_masked), mask # type: ignore[no-untyped-call]
86
+
87
+
88
+ def contour_generator(
89
+ x: ArrayLike | None = None,
90
+ y: ArrayLike | None = None,
91
+ z: ArrayLike | np.ma.MaskedArray[Any, Any] | None = None,
92
+ *,
93
+ name: str = "serial",
94
+ corner_mask: bool | None = None,
95
+ line_type: LineType | str | None = None,
96
+ fill_type: FillType | str | None = None,
97
+ chunk_size: int | tuple[int, int] | None = None,
98
+ chunk_count: int | tuple[int, int] | None = None,
99
+ total_chunk_count: int | None = None,
100
+ quad_as_tri: bool = False,
101
+ z_interp: ZInterp | str | None = ZInterp.Linear,
102
+ thread_count: int = 0,
103
+ ) -> ContourGenerator:
104
+ """Create and return a :class:`~.ContourGenerator` object.
105
+
106
+ The class and properties of the returned :class:`~.ContourGenerator` are determined by the
107
+ function arguments, with sensible defaults.
108
+
109
+ Args:
110
+ x (array-like of shape (ny, nx) or (nx,), optional): The x-coordinates of the ``z`` values.
111
+ May be 2D with the same shape as ``z.shape``, or 1D with length ``nx = z.shape[1]``.
112
+ If not specified are assumed to be ``np.arange(nx)``. Must be ordered monotonically.
113
+ y (array-like of shape (ny, nx) or (ny,), optional): The y-coordinates of the ``z`` values.
114
+ May be 2D with the same shape as ``z.shape``, or 1D with length ``ny = z.shape[0]``.
115
+ If not specified are assumed to be ``np.arange(ny)``. Must be ordered monotonically.
116
+ z (array-like of shape (ny, nx), may be a masked array): The 2D gridded values to calculate
117
+ the contours of. May be a masked array, and any invalid values (``np.inf`` or
118
+ ``np.nan``) will also be masked out.
119
+ name (str): Algorithm name, one of ``"serial"``, ``"threaded"``, ``"mpl2005"`` or
120
+ ``"mpl2014"``, default ``"serial"``.
121
+ corner_mask (bool, optional): Enable/disable corner masking, which only has an effect if
122
+ ``z`` is a masked array. If ``False``, any quad touching a masked point is masked out.
123
+ If ``True``, only the triangular corners of quads nearest these points are always masked
124
+ out, other triangular corners comprising three unmasked points are contoured as usual.
125
+ If not specified, uses the default provided by the algorithm ``name``.
126
+ line_type (LineType or str, optional): The format of contour line data returned from calls
127
+ to :meth:`~.ContourGenerator.lines`, specified either as a :class:`~.LineType` or its
128
+ string equivalent such as ``"SeparateCode"``.
129
+ If not specified, uses the default provided by the algorithm ``name``.
130
+ The relationship between the :class:`~.LineType` enum and the data format returned from
131
+ :meth:`~.ContourGenerator.lines` is explained at :ref:`line_type`.
132
+ fill_type (FillType or str, optional): The format of filled contour data returned from calls
133
+ to :meth:`~.ContourGenerator.filled`, specified either as a :class:`~.FillType` or its
134
+ string equivalent such as ``"OuterOffset"``.
135
+ If not specified, uses the default provided by the algorithm ``name``.
136
+ The relationship between the :class:`~.FillType` enum and the data format returned from
137
+ :meth:`~.ContourGenerator.filled` is explained at :ref:`fill_type`.
138
+ chunk_size (int or tuple(int, int), optional): Chunk size in (y, x) directions, or the same
139
+ size in both directions if only one value is specified.
140
+ chunk_count (int or tuple(int, int), optional): Chunk count in (y, x) directions, or the
141
+ same count in both directions if only one value is specified.
142
+ total_chunk_count (int, optional): Total number of chunks.
143
+ quad_as_tri (bool): Enable/disable treating quads as 4 triangles, default ``False``.
144
+ If ``False``, a contour line within a quad is a straight line between points on two of
145
+ its edges. If ``True``, each full quad is divided into 4 triangles using a virtual point
146
+ at the centre (mean x, y of the corner points) and a contour line is piecewise linear
147
+ within those triangles. Corner-masked triangles are not affected by this setting, only
148
+ full unmasked quads.
149
+ z_interp (ZInterp or str, optional): How to interpolate ``z`` values when determining where
150
+ contour lines intersect the edges of quads and the ``z`` values of the central points of
151
+ quads, specified either as a :class:`~contourpy.ZInterp` or its string equivalent such
152
+ as ``"Log"``. Default is ``ZInterp.Linear``.
153
+ thread_count (int): Number of threads to use for contour calculation, default 0. Threads can
154
+ only be used with an algorithm ``name`` that supports threads (currently only
155
+ ``name="threaded"``) and there must be at least the same number of chunks as threads.
156
+ If ``thread_count=0`` and ``name="threaded"`` then it uses the maximum number of threads
157
+ as determined by the C++11 call ``std::thread::hardware_concurrency()``. If ``name`` is
158
+ something other than ``"threaded"`` then the ``thread_count`` will be set to ``1``.
159
+
160
+ Return:
161
+ :class:`~.ContourGenerator`.
162
+
163
+ Note:
164
+ A maximum of one of ``chunk_size``, ``chunk_count`` and ``total_chunk_count`` may be
165
+ specified.
166
+
167
+ Warning:
168
+ The ``name="mpl2005"`` algorithm does not implement chunking for contour lines.
169
+ """
170
+ x = np.asarray(x, dtype=np.float64)
171
+ y = np.asarray(y, dtype=np.float64)
172
+ z, mask = _remove_z_mask(z)
173
+
174
+ # Check arguments: z.
175
+ if z.ndim != 2:
176
+ raise TypeError(f"Input z must be 2D, not {z.ndim}D")
177
+
178
+ if z.shape[0] < 2 or z.shape[1] < 2:
179
+ raise TypeError(f"Input z must be at least a (2, 2) shaped array, but has shape {z.shape}")
180
+
181
+ ny, nx = z.shape
182
+
183
+ # Check arguments: x and y.
184
+ if x.ndim != y.ndim:
185
+ raise TypeError(f"Number of dimensions of x ({x.ndim}) and y ({y.ndim}) do not match")
186
+
187
+ if x.ndim == 0:
188
+ x = np.arange(nx, dtype=np.float64)
189
+ y = np.arange(ny, dtype=np.float64)
190
+ x, y = np.meshgrid(x, y)
191
+ elif x.ndim == 1:
192
+ if len(x) != nx:
193
+ raise TypeError(f"Length of x ({len(x)}) must match number of columns in z ({nx})")
194
+ if len(y) != ny:
195
+ raise TypeError(f"Length of y ({len(y)}) must match number of rows in z ({ny})")
196
+ x, y = np.meshgrid(x, y)
197
+ elif x.ndim == 2:
198
+ if x.shape != z.shape:
199
+ raise TypeError(f"Shapes of x {x.shape} and z {z.shape} do not match")
200
+ if y.shape != z.shape:
201
+ raise TypeError(f"Shapes of y {y.shape} and z {z.shape} do not match")
202
+ else:
203
+ raise TypeError(f"Inputs x and y must be None, 1D or 2D, not {x.ndim}D")
204
+
205
+ # Check mask shape just in case.
206
+ if mask is not None and mask.shape != z.shape:
207
+ raise ValueError("If mask is set it must be a 2D array with the same shape as z")
208
+
209
+ # Check arguments: name.
210
+ if name not in _class_lookup:
211
+ raise ValueError(f"Unrecognised contour generator name: {name}")
212
+
213
+ # Check arguments: chunk_size, chunk_count and total_chunk_count.
214
+ y_chunk_size, x_chunk_size = calc_chunk_sizes(
215
+ chunk_size, chunk_count, total_chunk_count, ny, nx)
216
+
217
+ cls = _class_lookup[name]
218
+
219
+ # Check arguments: corner_mask.
220
+ if corner_mask is None:
221
+ # Set it to default, which is True if the algorithm supports it.
222
+ corner_mask = cls.supports_corner_mask()
223
+ elif corner_mask and not cls.supports_corner_mask():
224
+ raise ValueError(f"{name} contour generator does not support corner_mask=True")
225
+
226
+ # Check arguments: line_type.
227
+ if line_type is None:
228
+ line_type = cls.default_line_type
229
+ else:
230
+ line_type = as_line_type(line_type)
231
+
232
+ if not cls.supports_line_type(line_type):
233
+ raise ValueError(f"{name} contour generator does not support line_type {line_type}")
234
+
235
+ # Check arguments: fill_type.
236
+ if fill_type is None:
237
+ fill_type = cls.default_fill_type
238
+ else:
239
+ fill_type = as_fill_type(fill_type)
240
+
241
+ if not cls.supports_fill_type(fill_type):
242
+ raise ValueError(f"{name} contour generator does not support fill_type {fill_type}")
243
+
244
+ # Check arguments: quad_as_tri.
245
+ if quad_as_tri and not cls.supports_quad_as_tri():
246
+ raise ValueError(f"{name} contour generator does not support quad_as_tri=True")
247
+
248
+ # Check arguments: z_interp.
249
+ if z_interp is None:
250
+ z_interp = ZInterp.Linear
251
+ else:
252
+ z_interp = as_z_interp(z_interp)
253
+
254
+ if z_interp != ZInterp.Linear and not cls.supports_z_interp():
255
+ raise ValueError(f"{name} contour generator does not support z_interp {z_interp}")
256
+
257
+ # Check arguments: thread_count.
258
+ if thread_count not in (0, 1) and not cls.supports_threads():
259
+ raise ValueError(f"{name} contour generator does not support thread_count {thread_count}")
260
+
261
+ # Prepare args and kwargs for contour generator constructor.
262
+ args = [x, y, z, mask]
263
+ kwargs: dict[str, int | bool | LineType | FillType | ZInterp] = {
264
+ "x_chunk_size": x_chunk_size,
265
+ "y_chunk_size": y_chunk_size,
266
+ }
267
+
268
+ if name not in ("mpl2005", "mpl2014"):
269
+ kwargs["line_type"] = line_type
270
+ kwargs["fill_type"] = fill_type
271
+
272
+ if cls.supports_corner_mask():
273
+ kwargs["corner_mask"] = corner_mask
274
+
275
+ if cls.supports_quad_as_tri():
276
+ kwargs["quad_as_tri"] = quad_as_tri
277
+
278
+ if cls.supports_z_interp():
279
+ kwargs["z_interp"] = z_interp
280
+
281
+ if cls.supports_threads():
282
+ kwargs["thread_count"] = thread_count
283
+
284
+ # Create contour generator.
285
+ return cls(*args, **kwargs)
lib/python3.12/site-packages/contourpy/__pycache__/__init__.cpython-312.pyc ADDED
Binary file (12.8 kB). View file
 
lib/python3.12/site-packages/contourpy/__pycache__/_version.cpython-312.pyc ADDED
Binary file (214 Bytes). View file
 
lib/python3.12/site-packages/contourpy/__pycache__/array.cpython-312.pyc ADDED
Binary file (12.7 kB). View file
 
lib/python3.12/site-packages/contourpy/__pycache__/chunk.cpython-312.pyc ADDED
Binary file (3.7 kB). View file
 
lib/python3.12/site-packages/contourpy/__pycache__/convert.cpython-312.pyc ADDED
Binary file (26.6 kB). View file
 
lib/python3.12/site-packages/contourpy/__pycache__/dechunk.cpython-312.pyc ADDED
Binary file (8.25 kB). View file
 
lib/python3.12/site-packages/contourpy/__pycache__/enum_util.cpython-312.pyc ADDED
Binary file (2.1 kB). View file
 
lib/python3.12/site-packages/contourpy/__pycache__/typecheck.cpython-312.pyc ADDED
Binary file (11.5 kB). View file
 
lib/python3.12/site-packages/contourpy/__pycache__/types.cpython-312.pyc ADDED
Binary file (491 Bytes). View file
 
lib/python3.12/site-packages/contourpy/_contourpy.pyi ADDED
@@ -0,0 +1,199 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import ClassVar, NoReturn, TypeAlias
2
+
3
+ import numpy as np
4
+ import numpy.typing as npt
5
+
6
+ import contourpy._contourpy as cpy
7
+
8
+ # Input numpy array types, the same as in common.h
9
+ CoordinateArray: TypeAlias = npt.NDArray[np.float64]
10
+ MaskArray: TypeAlias = npt.NDArray[np.bool_]
11
+ LevelArray: TypeAlias = npt.ArrayLike
12
+
13
+ # Output numpy array types, the same as in common.h
14
+ PointArray: TypeAlias = npt.NDArray[np.float64]
15
+ CodeArray: TypeAlias = npt.NDArray[np.uint8]
16
+ OffsetArray: TypeAlias = npt.NDArray[np.uint32]
17
+
18
+ # Types returned from filled()
19
+ FillReturn_OuterCode: TypeAlias = tuple[list[PointArray], list[CodeArray]]
20
+ FillReturn_OuterOffset: TypeAlias = tuple[list[PointArray], list[OffsetArray]]
21
+ FillReturn_ChunkCombinedCode: TypeAlias = tuple[list[PointArray | None], list[CodeArray | None]]
22
+ FillReturn_ChunkCombinedOffset: TypeAlias = tuple[list[PointArray | None], list[OffsetArray | None]]
23
+ FillReturn_ChunkCombinedCodeOffset: TypeAlias = tuple[list[PointArray | None], list[CodeArray | None], list[OffsetArray | None]]
24
+ FillReturn_ChunkCombinedOffsetOffset: TypeAlias = tuple[list[PointArray | None], list[OffsetArray | None], list[OffsetArray | None]]
25
+ FillReturn_Chunk: TypeAlias = FillReturn_ChunkCombinedCode | FillReturn_ChunkCombinedOffset | FillReturn_ChunkCombinedCodeOffset | FillReturn_ChunkCombinedOffsetOffset
26
+ FillReturn: TypeAlias = FillReturn_OuterCode | FillReturn_OuterOffset | FillReturn_Chunk
27
+
28
+ # Types returned from lines()
29
+ LineReturn_Separate: TypeAlias = list[PointArray]
30
+ LineReturn_SeparateCode: TypeAlias = tuple[list[PointArray], list[CodeArray]]
31
+ LineReturn_ChunkCombinedCode: TypeAlias = tuple[list[PointArray | None], list[CodeArray | None]]
32
+ LineReturn_ChunkCombinedOffset: TypeAlias = tuple[list[PointArray | None], list[OffsetArray | None]]
33
+ LineReturn_ChunkCombinedNan: TypeAlias = tuple[list[PointArray | None]]
34
+ LineReturn_Chunk: TypeAlias = LineReturn_ChunkCombinedCode | LineReturn_ChunkCombinedOffset | LineReturn_ChunkCombinedNan
35
+ LineReturn: TypeAlias = LineReturn_Separate | LineReturn_SeparateCode | LineReturn_Chunk
36
+
37
+
38
+ NDEBUG: int
39
+ __version__: str
40
+
41
+ class FillType:
42
+ ChunkCombinedCode: ClassVar[cpy.FillType]
43
+ ChunkCombinedCodeOffset: ClassVar[cpy.FillType]
44
+ ChunkCombinedOffset: ClassVar[cpy.FillType]
45
+ ChunkCombinedOffsetOffset: ClassVar[cpy.FillType]
46
+ OuterCode: ClassVar[cpy.FillType]
47
+ OuterOffset: ClassVar[cpy.FillType]
48
+ __members__: ClassVar[dict[str, cpy.FillType]]
49
+ def __eq__(self, other: object) -> bool: ...
50
+ def __getstate__(self) -> int: ...
51
+ def __hash__(self) -> int: ...
52
+ def __index__(self) -> int: ...
53
+ def __init__(self, value: int) -> None: ...
54
+ def __int__(self) -> int: ...
55
+ def __ne__(self, other: object) -> bool: ...
56
+ def __setstate__(self, state: int) -> NoReturn: ...
57
+ @property
58
+ def name(self) -> str: ...
59
+ @property
60
+ def value(self) -> int: ...
61
+
62
+ class LineType:
63
+ ChunkCombinedCode: ClassVar[cpy.LineType]
64
+ ChunkCombinedNan: ClassVar[cpy.LineType]
65
+ ChunkCombinedOffset: ClassVar[cpy.LineType]
66
+ Separate: ClassVar[cpy.LineType]
67
+ SeparateCode: ClassVar[cpy.LineType]
68
+ __members__: ClassVar[dict[str, cpy.LineType]]
69
+ def __eq__(self, other: object) -> bool: ...
70
+ def __getstate__(self) -> int: ...
71
+ def __hash__(self) -> int: ...
72
+ def __index__(self) -> int: ...
73
+ def __init__(self, value: int) -> None: ...
74
+ def __int__(self) -> int: ...
75
+ def __ne__(self, other: object) -> bool: ...
76
+ def __setstate__(self, state: int) -> NoReturn: ...
77
+ @property
78
+ def name(self) -> str: ...
79
+ @property
80
+ def value(self) -> int: ...
81
+
82
+ class ZInterp:
83
+ Linear: ClassVar[cpy.ZInterp]
84
+ Log: ClassVar[cpy.ZInterp]
85
+ __members__: ClassVar[dict[str, cpy.ZInterp]]
86
+ def __eq__(self, other: object) -> bool: ...
87
+ def __getstate__(self) -> int: ...
88
+ def __hash__(self) -> int: ...
89
+ def __index__(self) -> int: ...
90
+ def __init__(self, value: int) -> None: ...
91
+ def __int__(self) -> int: ...
92
+ def __ne__(self, other: object) -> bool: ...
93
+ def __setstate__(self, state: int) -> NoReturn: ...
94
+ @property
95
+ def name(self) -> str: ...
96
+ @property
97
+ def value(self) -> int: ...
98
+
99
+ def max_threads() -> int: ...
100
+
101
+ class ContourGenerator:
102
+ def create_contour(self, level: float) -> LineReturn: ...
103
+ def create_filled_contour(self, lower_level: float, upper_level: float) -> FillReturn: ...
104
+ def filled(self, lower_level: float, upper_level: float) -> FillReturn: ...
105
+ def lines(self, level: float) -> LineReturn: ...
106
+ def multi_filled(self, levels: LevelArray) -> list[FillReturn]: ...
107
+ def multi_lines(self, levels: LevelArray) -> list[LineReturn]: ...
108
+ @staticmethod
109
+ def supports_corner_mask() -> bool: ...
110
+ @staticmethod
111
+ def supports_fill_type(fill_type: FillType) -> bool: ...
112
+ @staticmethod
113
+ def supports_line_type(line_type: LineType) -> bool: ...
114
+ @staticmethod
115
+ def supports_quad_as_tri() -> bool: ...
116
+ @staticmethod
117
+ def supports_threads() -> bool: ...
118
+ @staticmethod
119
+ def supports_z_interp() -> bool: ...
120
+ @property
121
+ def chunk_count(self) -> tuple[int, int]: ...
122
+ @property
123
+ def chunk_size(self) -> tuple[int, int]: ...
124
+ @property
125
+ def corner_mask(self) -> bool: ...
126
+ @property
127
+ def fill_type(self) -> FillType: ...
128
+ @property
129
+ def line_type(self) -> LineType: ...
130
+ @property
131
+ def quad_as_tri(self) -> bool: ...
132
+ @property
133
+ def thread_count(self) -> int: ...
134
+ @property
135
+ def z_interp(self) -> ZInterp: ...
136
+ default_fill_type: cpy.FillType
137
+ default_line_type: cpy.LineType
138
+
139
+ class Mpl2005ContourGenerator(ContourGenerator):
140
+ def __init__(
141
+ self,
142
+ x: CoordinateArray,
143
+ y: CoordinateArray,
144
+ z: CoordinateArray,
145
+ mask: MaskArray,
146
+ *,
147
+ x_chunk_size: int = 0,
148
+ y_chunk_size: int = 0,
149
+ ) -> None: ...
150
+
151
+ class Mpl2014ContourGenerator(ContourGenerator):
152
+ def __init__(
153
+ self,
154
+ x: CoordinateArray,
155
+ y: CoordinateArray,
156
+ z: CoordinateArray,
157
+ mask: MaskArray,
158
+ *,
159
+ corner_mask: bool,
160
+ x_chunk_size: int = 0,
161
+ y_chunk_size: int = 0,
162
+ ) -> None: ...
163
+
164
+ class SerialContourGenerator(ContourGenerator):
165
+ def __init__(
166
+ self,
167
+ x: CoordinateArray,
168
+ y: CoordinateArray,
169
+ z: CoordinateArray,
170
+ mask: MaskArray,
171
+ *,
172
+ corner_mask: bool,
173
+ line_type: LineType,
174
+ fill_type: FillType,
175
+ quad_as_tri: bool,
176
+ z_interp: ZInterp,
177
+ x_chunk_size: int = 0,
178
+ y_chunk_size: int = 0,
179
+ ) -> None: ...
180
+ def _write_cache(self) -> NoReturn: ...
181
+
182
+ class ThreadedContourGenerator(ContourGenerator):
183
+ def __init__(
184
+ self,
185
+ x: CoordinateArray,
186
+ y: CoordinateArray,
187
+ z: CoordinateArray,
188
+ mask: MaskArray,
189
+ *,
190
+ corner_mask: bool,
191
+ line_type: LineType,
192
+ fill_type: FillType,
193
+ quad_as_tri: bool,
194
+ z_interp: ZInterp,
195
+ x_chunk_size: int = 0,
196
+ y_chunk_size: int = 0,
197
+ thread_count: int = 0,
198
+ ) -> None: ...
199
+ def _write_cache(self) -> None: ...
lib/python3.12/site-packages/contourpy/_version.py ADDED
@@ -0,0 +1 @@
 
 
1
+ __version__ = "1.3.3"
lib/python3.12/site-packages/contourpy/array.py ADDED
@@ -0,0 +1,261 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ from itertools import chain, pairwise
4
+ from typing import TYPE_CHECKING
5
+
6
+ import numpy as np
7
+
8
+ from contourpy.typecheck import check_code_array, check_offset_array, check_point_array
9
+ from contourpy.types import CLOSEPOLY, LINETO, MOVETO, code_dtype, offset_dtype, point_dtype
10
+
11
+ if TYPE_CHECKING:
12
+ import contourpy._contourpy as cpy
13
+
14
+
15
+ def codes_from_offsets(offsets: cpy.OffsetArray) -> cpy.CodeArray:
16
+ """Determine codes from offsets, assuming they all correspond to closed polygons.
17
+ """
18
+ check_offset_array(offsets)
19
+
20
+ n = offsets[-1]
21
+ codes = np.full(n, LINETO, dtype=code_dtype)
22
+ codes[offsets[:-1]] = MOVETO
23
+ codes[offsets[1:] - 1] = CLOSEPOLY
24
+ return codes
25
+
26
+
27
+ def codes_from_offsets_and_points(
28
+ offsets: cpy.OffsetArray,
29
+ points: cpy.PointArray,
30
+ ) -> cpy.CodeArray:
31
+ """Determine codes from offsets and points, using the equality of the start and end points of
32
+ each line to determine if lines are closed or not.
33
+ """
34
+ check_offset_array(offsets)
35
+ check_point_array(points)
36
+
37
+ codes = np.full(len(points), LINETO, dtype=code_dtype)
38
+ codes[offsets[:-1]] = MOVETO
39
+
40
+ end_offsets = offsets[1:] - 1
41
+ closed = np.all(points[offsets[:-1]] == points[end_offsets], axis=1)
42
+ codes[end_offsets[closed]] = CLOSEPOLY
43
+
44
+ return codes
45
+
46
+
47
+ def codes_from_points(points: cpy.PointArray) -> cpy.CodeArray:
48
+ """Determine codes for a single line, using the equality of the start and end points to
49
+ determine if the line is closed or not.
50
+ """
51
+ check_point_array(points)
52
+
53
+ n = len(points)
54
+ codes = np.full(n, LINETO, dtype=code_dtype)
55
+ codes[0] = MOVETO
56
+ if np.all(points[0] == points[-1]):
57
+ codes[-1] = CLOSEPOLY
58
+ return codes
59
+
60
+
61
+ def concat_codes(list_of_codes: list[cpy.CodeArray]) -> cpy.CodeArray:
62
+ """Concatenate a list of codes arrays into a single code array.
63
+ """
64
+ if not list_of_codes:
65
+ raise ValueError("Empty list passed to concat_codes")
66
+
67
+ return np.concatenate(list_of_codes, dtype=code_dtype)
68
+
69
+
70
+ def concat_codes_or_none(list_of_codes_or_none: list[cpy.CodeArray | None]) -> cpy.CodeArray | None:
71
+ """Concatenate a list of codes arrays or None into a single code array or None.
72
+ """
73
+ list_of_codes = [codes for codes in list_of_codes_or_none if codes is not None]
74
+ if list_of_codes:
75
+ return concat_codes(list_of_codes)
76
+ else:
77
+ return None
78
+
79
+
80
+ def concat_offsets(list_of_offsets: list[cpy.OffsetArray]) -> cpy.OffsetArray:
81
+ """Concatenate a list of offsets arrays into a single offset array.
82
+ """
83
+ if not list_of_offsets:
84
+ raise ValueError("Empty list passed to concat_offsets")
85
+
86
+ n = len(list_of_offsets)
87
+ cumulative = np.cumsum([offsets[-1] for offsets in list_of_offsets], dtype=offset_dtype)
88
+ ret: cpy.OffsetArray = np.concatenate(
89
+ (list_of_offsets[0], *(list_of_offsets[i+1][1:] + cumulative[i] for i in range(n-1))),
90
+ dtype=offset_dtype,
91
+ )
92
+ return ret
93
+
94
+
95
+ def concat_offsets_or_none(
96
+ list_of_offsets_or_none: list[cpy.OffsetArray | None],
97
+ ) -> cpy.OffsetArray | None:
98
+ """Concatenate a list of offsets arrays or None into a single offset array or None.
99
+ """
100
+ list_of_offsets = [offsets for offsets in list_of_offsets_or_none if offsets is not None]
101
+ if list_of_offsets:
102
+ return concat_offsets(list_of_offsets)
103
+ else:
104
+ return None
105
+
106
+
107
+ def concat_points(list_of_points: list[cpy.PointArray]) -> cpy.PointArray:
108
+ """Concatenate a list of point arrays into a single point array.
109
+ """
110
+ if not list_of_points:
111
+ raise ValueError("Empty list passed to concat_points")
112
+
113
+ return np.concatenate(list_of_points, dtype=point_dtype)
114
+
115
+
116
+ def concat_points_or_none(
117
+ list_of_points_or_none: list[cpy.PointArray | None],
118
+ ) -> cpy.PointArray | None:
119
+ """Concatenate a list of point arrays or None into a single point array or None.
120
+ """
121
+ list_of_points = [points for points in list_of_points_or_none if points is not None]
122
+ if list_of_points:
123
+ return concat_points(list_of_points)
124
+ else:
125
+ return None
126
+
127
+
128
+ def concat_points_or_none_with_nan(
129
+ list_of_points_or_none: list[cpy.PointArray | None],
130
+ ) -> cpy.PointArray | None:
131
+ """Concatenate a list of points or None into a single point array or None, with NaNs used to
132
+ separate each line.
133
+ """
134
+ list_of_points = [points for points in list_of_points_or_none if points is not None]
135
+ if list_of_points:
136
+ return concat_points_with_nan(list_of_points)
137
+ else:
138
+ return None
139
+
140
+
141
+ def concat_points_with_nan(list_of_points: list[cpy.PointArray]) -> cpy.PointArray:
142
+ """Concatenate a list of points into a single point array with NaNs used to separate each line.
143
+ """
144
+ if not list_of_points:
145
+ raise ValueError("Empty list passed to concat_points_with_nan")
146
+
147
+ if len(list_of_points) == 1:
148
+ return list_of_points[0]
149
+ else:
150
+ nan_spacer = np.full((1, 2), np.nan, dtype=point_dtype)
151
+ list_of_points = [list_of_points[0],
152
+ *list(chain(*((nan_spacer, x) for x in list_of_points[1:])))]
153
+ return concat_points(list_of_points)
154
+
155
+
156
+ def insert_nan_at_offsets(points: cpy.PointArray, offsets: cpy.OffsetArray) -> cpy.PointArray:
157
+ """Insert NaNs into a point array at locations specified by an offset array.
158
+ """
159
+ check_point_array(points)
160
+ check_offset_array(offsets)
161
+
162
+ if len(offsets) <= 2:
163
+ return points
164
+ else:
165
+ nan_spacer = np.array([np.nan, np.nan], dtype=point_dtype)
166
+ # Convert offsets to int64 to avoid numpy error when mixing signed and unsigned ints.
167
+ return np.insert(points, offsets[1:-1].astype(np.int64), nan_spacer, axis=0)
168
+
169
+
170
+ def offsets_from_codes(codes: cpy.CodeArray) -> cpy.OffsetArray:
171
+ """Determine offsets from codes using locations of MOVETO codes.
172
+ """
173
+ check_code_array(codes)
174
+
175
+ return np.append(np.nonzero(codes == MOVETO)[0], len(codes)).astype(offset_dtype)
176
+
177
+
178
+ def offsets_from_lengths(list_of_points: list[cpy.PointArray]) -> cpy.OffsetArray:
179
+ """Determine offsets from lengths of point arrays.
180
+ """
181
+ if not list_of_points:
182
+ raise ValueError("Empty list passed to offsets_from_lengths")
183
+
184
+ return np.cumsum([0] + [len(line) for line in list_of_points], dtype=offset_dtype)
185
+
186
+
187
+ def outer_offsets_from_list_of_codes(list_of_codes: list[cpy.CodeArray]) -> cpy.OffsetArray:
188
+ """Determine outer offsets from codes using locations of MOVETO codes.
189
+ """
190
+ if not list_of_codes:
191
+ raise ValueError("Empty list passed to outer_offsets_from_list_of_codes")
192
+
193
+ return np.cumsum([0] + [np.count_nonzero(codes == MOVETO) for codes in list_of_codes],
194
+ dtype=offset_dtype)
195
+
196
+
197
+ def outer_offsets_from_list_of_offsets(list_of_offsets: list[cpy.OffsetArray]) -> cpy.OffsetArray:
198
+ """Determine outer offsets from a list of offsets.
199
+ """
200
+ if not list_of_offsets:
201
+ raise ValueError("Empty list passed to outer_offsets_from_list_of_offsets")
202
+
203
+ return np.cumsum([0] + [len(offsets)-1 for offsets in list_of_offsets], dtype=offset_dtype)
204
+
205
+
206
+ def remove_nan(points: cpy.PointArray) -> tuple[cpy.PointArray, cpy.OffsetArray]:
207
+ """Remove NaN from a points array, also return the offsets corresponding to the NaN removed.
208
+ """
209
+ check_point_array(points)
210
+
211
+ nan_offsets = np.nonzero(np.isnan(points[:, 0]))[0]
212
+ if len(nan_offsets) == 0:
213
+ return points, np.array([0, len(points)], dtype=offset_dtype)
214
+ else:
215
+ points = np.delete(points, nan_offsets, axis=0)
216
+ nan_offsets -= np.arange(len(nan_offsets))
217
+ offsets: cpy.OffsetArray = np.empty(len(nan_offsets)+2, dtype=offset_dtype)
218
+ offsets[0] = 0
219
+ offsets[1:-1] = nan_offsets
220
+ offsets[-1] = len(points)
221
+ return points, offsets
222
+
223
+
224
+ def split_codes_by_offsets(codes: cpy.CodeArray, offsets: cpy.OffsetArray) -> list[cpy.CodeArray]:
225
+ """Split a code array at locations specified by an offset array into a list of code arrays.
226
+ """
227
+ check_code_array(codes)
228
+ check_offset_array(offsets)
229
+
230
+ if len(offsets) > 2:
231
+ return np.split(codes, offsets[1:-1])
232
+ else:
233
+ return [codes]
234
+
235
+
236
+ def split_points_by_offsets(
237
+ points: cpy.PointArray,
238
+ offsets: cpy.OffsetArray,
239
+ ) -> list[cpy.PointArray]:
240
+ """Split a point array at locations specified by an offset array into a list of point arrays.
241
+ """
242
+ check_point_array(points)
243
+ check_offset_array(offsets)
244
+
245
+ if len(offsets) > 2:
246
+ return np.split(points, offsets[1:-1])
247
+ else:
248
+ return [points]
249
+
250
+
251
+ def split_points_at_nan(points: cpy.PointArray) -> list[cpy.PointArray]:
252
+ """Split a points array at NaNs into a list of point arrays.
253
+ """
254
+ check_point_array(points)
255
+
256
+ nan_offsets = np.nonzero(np.isnan(points[:, 0]))[0]
257
+ if len(nan_offsets) == 0:
258
+ return [points]
259
+ else:
260
+ nan_offsets = np.concatenate(([-1], nan_offsets, [len(points)]))
261
+ return [points[s+1:e] for s, e in pairwise(nan_offsets)]
lib/python3.12/site-packages/contourpy/chunk.py ADDED
@@ -0,0 +1,95 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import math
4
+
5
+
6
+ def calc_chunk_sizes(
7
+ chunk_size: int | tuple[int, int] | None,
8
+ chunk_count: int | tuple[int, int] | None,
9
+ total_chunk_count: int | None,
10
+ ny: int,
11
+ nx: int,
12
+ ) -> tuple[int, int]:
13
+ """Calculate chunk sizes.
14
+
15
+ Args:
16
+ chunk_size (int or tuple(int, int), optional): Chunk size in (y, x) directions, or the same
17
+ size in both directions if only one is specified. Cannot be negative.
18
+ chunk_count (int or tuple(int, int), optional): Chunk count in (y, x) directions, or the
19
+ same count in both directions if only one is specified. If less than 1, set to 1.
20
+ total_chunk_count (int, optional): Total number of chunks. If less than 1, set to 1.
21
+ ny (int): Number of grid points in y-direction.
22
+ nx (int): Number of grid points in x-direction.
23
+
24
+ Return:
25
+ tuple(int, int): Chunk sizes (y_chunk_size, x_chunk_size).
26
+
27
+ Note:
28
+ Zero or one of ``chunk_size``, ``chunk_count`` and ``total_chunk_count`` should be
29
+ specified.
30
+ """
31
+ if sum([chunk_size is not None, chunk_count is not None, total_chunk_count is not None]) > 1:
32
+ raise ValueError("Only one of chunk_size, chunk_count and total_chunk_count should be set")
33
+
34
+ if nx < 2 or ny < 2:
35
+ raise ValueError(f"(ny, nx) must be at least (2, 2), not ({ny}, {nx})")
36
+
37
+ if total_chunk_count is not None:
38
+ max_chunk_count = (nx-1)*(ny-1)
39
+ total_chunk_count = min(max(total_chunk_count, 1), max_chunk_count)
40
+ if total_chunk_count == 1:
41
+ chunk_size = 0
42
+ elif total_chunk_count == max_chunk_count:
43
+ chunk_size = (1, 1)
44
+ else:
45
+ factors = two_factors(total_chunk_count)
46
+ if ny > nx:
47
+ chunk_count = factors
48
+ else:
49
+ chunk_count = (factors[1], factors[0])
50
+
51
+ if chunk_count is not None:
52
+ if isinstance(chunk_count, tuple):
53
+ y_chunk_count, x_chunk_count = chunk_count
54
+ else:
55
+ y_chunk_count = x_chunk_count = chunk_count
56
+ x_chunk_count = min(max(x_chunk_count, 1), nx-1)
57
+ y_chunk_count = min(max(y_chunk_count, 1), ny-1)
58
+ chunk_size = (math.ceil((ny-1) / y_chunk_count), math.ceil((nx-1) / x_chunk_count))
59
+
60
+ if chunk_size is None:
61
+ y_chunk_size = x_chunk_size = 0
62
+ elif isinstance(chunk_size, tuple):
63
+ y_chunk_size, x_chunk_size = chunk_size
64
+ else:
65
+ y_chunk_size = x_chunk_size = chunk_size
66
+
67
+ if x_chunk_size < 0 or y_chunk_size < 0:
68
+ raise ValueError("chunk_size cannot be negative")
69
+
70
+ return y_chunk_size, x_chunk_size
71
+
72
+
73
+ def two_factors(n: int) -> tuple[int, int]:
74
+ """Split an integer into two integer factors.
75
+
76
+ The two factors will be as close as possible to the sqrt of n, and are returned in decreasing
77
+ order. Worst case returns (n, 1).
78
+
79
+ Args:
80
+ n (int): The integer to factorize, must be positive.
81
+
82
+ Return:
83
+ tuple(int, int): The two factors of n, in decreasing order.
84
+ """
85
+ if n < 0:
86
+ raise ValueError(f"two_factors expects positive integer not {n}")
87
+
88
+ i = math.ceil(math.sqrt(n))
89
+ while n % i != 0:
90
+ i -= 1
91
+ j = n // i
92
+ if i > j:
93
+ return i, j
94
+ else:
95
+ return j, i
lib/python3.12/site-packages/contourpy/convert.py ADDED
@@ -0,0 +1,621 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ from itertools import pairwise
4
+ from typing import TYPE_CHECKING, cast
5
+
6
+ import numpy as np
7
+
8
+ from contourpy._contourpy import FillType, LineType
9
+ import contourpy.array as arr
10
+ from contourpy.enum_util import as_fill_type, as_line_type
11
+ from contourpy.typecheck import check_filled, check_lines
12
+ from contourpy.types import MOVETO, offset_dtype
13
+
14
+ if TYPE_CHECKING:
15
+ import contourpy._contourpy as cpy
16
+
17
+
18
+ def _convert_filled_from_OuterCode(
19
+ filled: cpy.FillReturn_OuterCode,
20
+ fill_type_to: FillType,
21
+ ) -> cpy.FillReturn:
22
+ if fill_type_to == FillType.OuterCode:
23
+ return filled
24
+ elif fill_type_to == FillType.OuterOffset:
25
+ return (filled[0], [arr.offsets_from_codes(codes) for codes in filled[1]])
26
+
27
+ if len(filled[0]) > 0:
28
+ points = arr.concat_points(filled[0])
29
+ codes = arr.concat_codes(filled[1])
30
+ else:
31
+ points = None
32
+ codes = None
33
+
34
+ if fill_type_to == FillType.ChunkCombinedCode:
35
+ return ([points], [codes])
36
+ elif fill_type_to == FillType.ChunkCombinedOffset:
37
+ return ([points], [None if codes is None else arr.offsets_from_codes(codes)])
38
+ elif fill_type_to == FillType.ChunkCombinedCodeOffset:
39
+ outer_offsets = None if points is None else arr.offsets_from_lengths(filled[0])
40
+ ret1: cpy.FillReturn_ChunkCombinedCodeOffset = ([points], [codes], [outer_offsets])
41
+ return ret1
42
+ elif fill_type_to == FillType.ChunkCombinedOffsetOffset:
43
+ if codes is None:
44
+ ret2: cpy.FillReturn_ChunkCombinedOffsetOffset = ([None], [None], [None])
45
+ else:
46
+ offsets = arr.offsets_from_codes(codes)
47
+ outer_offsets = arr.outer_offsets_from_list_of_codes(filled[1])
48
+ ret2 = ([points], [offsets], [outer_offsets])
49
+ return ret2
50
+ else:
51
+ raise ValueError(f"Invalid FillType {fill_type_to}")
52
+
53
+
54
+ def _convert_filled_from_OuterOffset(
55
+ filled: cpy.FillReturn_OuterOffset,
56
+ fill_type_to: FillType,
57
+ ) -> cpy.FillReturn:
58
+ if fill_type_to == FillType.OuterCode:
59
+ separate_codes = [arr.codes_from_offsets(offsets) for offsets in filled[1]]
60
+ return (filled[0], separate_codes)
61
+ elif fill_type_to == FillType.OuterOffset:
62
+ return filled
63
+
64
+ if len(filled[0]) > 0:
65
+ points = arr.concat_points(filled[0])
66
+ offsets = arr.concat_offsets(filled[1])
67
+ else:
68
+ points = None
69
+ offsets = None
70
+
71
+ if fill_type_to == FillType.ChunkCombinedCode:
72
+ return ([points], [None if offsets is None else arr.codes_from_offsets(offsets)])
73
+ elif fill_type_to == FillType.ChunkCombinedOffset:
74
+ return ([points], [offsets])
75
+ elif fill_type_to == FillType.ChunkCombinedCodeOffset:
76
+ if offsets is None:
77
+ ret1: cpy.FillReturn_ChunkCombinedCodeOffset = ([None], [None], [None])
78
+ else:
79
+ codes = arr.codes_from_offsets(offsets)
80
+ outer_offsets = arr.offsets_from_lengths(filled[0])
81
+ ret1 = ([points], [codes], [outer_offsets])
82
+ return ret1
83
+ elif fill_type_to == FillType.ChunkCombinedOffsetOffset:
84
+ if points is None:
85
+ ret2: cpy.FillReturn_ChunkCombinedOffsetOffset = ([None], [None], [None])
86
+ else:
87
+ outer_offsets = arr.outer_offsets_from_list_of_offsets(filled[1])
88
+ ret2 = ([points], [offsets], [outer_offsets])
89
+ return ret2
90
+ else:
91
+ raise ValueError(f"Invalid FillType {fill_type_to}")
92
+
93
+
94
+ def _convert_filled_from_ChunkCombinedCode(
95
+ filled: cpy.FillReturn_ChunkCombinedCode,
96
+ fill_type_to: FillType,
97
+ ) -> cpy.FillReturn:
98
+ if fill_type_to == FillType.ChunkCombinedCode:
99
+ return filled
100
+ elif fill_type_to == FillType.ChunkCombinedOffset:
101
+ codes = [None if codes is None else arr.offsets_from_codes(codes) for codes in filled[1]]
102
+ return (filled[0], codes)
103
+ else:
104
+ raise ValueError(
105
+ f"Conversion from {FillType.ChunkCombinedCode} to {fill_type_to} not supported")
106
+
107
+
108
+ def _convert_filled_from_ChunkCombinedOffset(
109
+ filled: cpy.FillReturn_ChunkCombinedOffset,
110
+ fill_type_to: FillType,
111
+ ) -> cpy.FillReturn:
112
+ if fill_type_to == FillType.ChunkCombinedCode:
113
+ chunk_codes: list[cpy.CodeArray | None] = []
114
+ for points, offsets in zip(*filled):
115
+ if points is None:
116
+ chunk_codes.append(None)
117
+ else:
118
+ if TYPE_CHECKING:
119
+ assert offsets is not None
120
+ chunk_codes.append(arr.codes_from_offsets_and_points(offsets, points))
121
+ return (filled[0], chunk_codes)
122
+ elif fill_type_to == FillType.ChunkCombinedOffset:
123
+ return filled
124
+ else:
125
+ raise ValueError(
126
+ f"Conversion from {FillType.ChunkCombinedOffset} to {fill_type_to} not supported")
127
+
128
+
129
+ def _convert_filled_from_ChunkCombinedCodeOffset(
130
+ filled: cpy.FillReturn_ChunkCombinedCodeOffset,
131
+ fill_type_to: FillType,
132
+ ) -> cpy.FillReturn:
133
+ if fill_type_to == FillType.OuterCode:
134
+ separate_points = []
135
+ separate_codes = []
136
+ for points, codes, outer_offsets in zip(*filled):
137
+ if points is not None:
138
+ if TYPE_CHECKING:
139
+ assert codes is not None
140
+ assert outer_offsets is not None
141
+ separate_points += arr.split_points_by_offsets(points, outer_offsets)
142
+ separate_codes += arr.split_codes_by_offsets(codes, outer_offsets)
143
+ return (separate_points, separate_codes)
144
+ elif fill_type_to == FillType.OuterOffset:
145
+ separate_points = []
146
+ separate_offsets = []
147
+ for points, codes, outer_offsets in zip(*filled):
148
+ if points is not None:
149
+ if TYPE_CHECKING:
150
+ assert codes is not None
151
+ assert outer_offsets is not None
152
+ separate_points += arr.split_points_by_offsets(points, outer_offsets)
153
+ separate_codes = arr.split_codes_by_offsets(codes, outer_offsets)
154
+ separate_offsets += [arr.offsets_from_codes(codes) for codes in separate_codes]
155
+ return (separate_points, separate_offsets)
156
+ elif fill_type_to == FillType.ChunkCombinedCode:
157
+ ret1: cpy.FillReturn_ChunkCombinedCode = (filled[0], filled[1])
158
+ return ret1
159
+ elif fill_type_to == FillType.ChunkCombinedOffset:
160
+ all_offsets = [None if codes is None else arr.offsets_from_codes(codes)
161
+ for codes in filled[1]]
162
+ ret2: cpy.FillReturn_ChunkCombinedOffset = (filled[0], all_offsets)
163
+ return ret2
164
+ elif fill_type_to == FillType.ChunkCombinedCodeOffset:
165
+ return filled
166
+ elif fill_type_to == FillType.ChunkCombinedOffsetOffset:
167
+ chunk_offsets: list[cpy.OffsetArray | None] = []
168
+ chunk_outer_offsets: list[cpy.OffsetArray | None] = []
169
+ for codes, outer_offsets in zip(*filled[1:]):
170
+ if codes is None:
171
+ chunk_offsets.append(None)
172
+ chunk_outer_offsets.append(None)
173
+ else:
174
+ if TYPE_CHECKING:
175
+ assert outer_offsets is not None
176
+ offsets = arr.offsets_from_codes(codes)
177
+ outer_offsets = np.array([np.nonzero(offsets == oo)[0][0] for oo in outer_offsets],
178
+ dtype=offset_dtype)
179
+ chunk_offsets.append(offsets)
180
+ chunk_outer_offsets.append(outer_offsets)
181
+ ret3: cpy.FillReturn_ChunkCombinedOffsetOffset = (
182
+ filled[0], chunk_offsets, chunk_outer_offsets,
183
+ )
184
+ return ret3
185
+ else:
186
+ raise ValueError(f"Invalid FillType {fill_type_to}")
187
+
188
+
189
+ def _convert_filled_from_ChunkCombinedOffsetOffset(
190
+ filled: cpy.FillReturn_ChunkCombinedOffsetOffset,
191
+ fill_type_to: FillType,
192
+ ) -> cpy.FillReturn:
193
+ if fill_type_to == FillType.OuterCode:
194
+ separate_points = []
195
+ separate_codes = []
196
+ for points, offsets, outer_offsets in zip(*filled):
197
+ if points is not None:
198
+ if TYPE_CHECKING:
199
+ assert offsets is not None
200
+ assert outer_offsets is not None
201
+ codes = arr.codes_from_offsets_and_points(offsets, points)
202
+ outer_offsets = offsets[outer_offsets]
203
+ separate_points += arr.split_points_by_offsets(points, outer_offsets)
204
+ separate_codes += arr.split_codes_by_offsets(codes, outer_offsets)
205
+ return (separate_points, separate_codes)
206
+ elif fill_type_to == FillType.OuterOffset:
207
+ separate_points = []
208
+ separate_offsets = []
209
+ for points, offsets, outer_offsets in zip(*filled):
210
+ if points is not None:
211
+ if TYPE_CHECKING:
212
+ assert offsets is not None
213
+ assert outer_offsets is not None
214
+ if len(outer_offsets) > 2:
215
+ separate_offsets += [offsets[s:e+1] - offsets[s] for s, e in
216
+ pairwise(outer_offsets)]
217
+ else:
218
+ separate_offsets.append(offsets)
219
+ separate_points += arr.split_points_by_offsets(points, offsets[outer_offsets])
220
+ return (separate_points, separate_offsets)
221
+ elif fill_type_to == FillType.ChunkCombinedCode:
222
+ chunk_codes: list[cpy.CodeArray | None] = []
223
+ for points, offsets, outer_offsets in zip(*filled):
224
+ if points is None:
225
+ chunk_codes.append(None)
226
+ else:
227
+ if TYPE_CHECKING:
228
+ assert offsets is not None
229
+ assert outer_offsets is not None
230
+ chunk_codes.append(arr.codes_from_offsets_and_points(offsets, points))
231
+ ret1: cpy.FillReturn_ChunkCombinedCode = (filled[0], chunk_codes)
232
+ return ret1
233
+ elif fill_type_to == FillType.ChunkCombinedOffset:
234
+ return (filled[0], filled[1])
235
+ elif fill_type_to == FillType.ChunkCombinedCodeOffset:
236
+ chunk_codes = []
237
+ chunk_outer_offsets: list[cpy.OffsetArray | None] = []
238
+ for points, offsets, outer_offsets in zip(*filled):
239
+ if points is None:
240
+ chunk_codes.append(None)
241
+ chunk_outer_offsets.append(None)
242
+ else:
243
+ if TYPE_CHECKING:
244
+ assert offsets is not None
245
+ assert outer_offsets is not None
246
+ chunk_codes.append(arr.codes_from_offsets_and_points(offsets, points))
247
+ chunk_outer_offsets.append(offsets[outer_offsets])
248
+ ret2: cpy.FillReturn_ChunkCombinedCodeOffset = (filled[0], chunk_codes, chunk_outer_offsets)
249
+ return ret2
250
+ elif fill_type_to == FillType.ChunkCombinedOffsetOffset:
251
+ return filled
252
+ else:
253
+ raise ValueError(f"Invalid FillType {fill_type_to}")
254
+
255
+
256
+ def convert_filled(
257
+ filled: cpy.FillReturn,
258
+ fill_type_from: FillType | str,
259
+ fill_type_to: FillType | str,
260
+ ) -> cpy.FillReturn:
261
+ """Convert filled contours from one :class:`~.FillType` to another.
262
+
263
+ Args:
264
+ filled (sequence of arrays): Filled contour polygons to convert, such as those returned by
265
+ :meth:`.ContourGenerator.filled`.
266
+ fill_type_from (FillType or str): :class:`~.FillType` to convert from as enum or
267
+ string equivalent.
268
+ fill_type_to (FillType or str): :class:`~.FillType` to convert to as enum or string
269
+ equivalent.
270
+
271
+ Return:
272
+ Converted filled contour polygons.
273
+
274
+ When converting non-chunked fill types (``FillType.OuterCode`` or ``FillType.OuterOffset``) to
275
+ chunked ones, all polygons are placed in the first chunk. When converting in the other
276
+ direction, all chunk information is discarded. Converting a fill type that is not aware of the
277
+ relationship between outer boundaries and contained holes (``FillType.ChunkCombinedCode`` or
278
+ ``FillType.ChunkCombinedOffset``) to one that is will raise a ``ValueError``.
279
+
280
+ .. versionadded:: 1.2.0
281
+ """
282
+ fill_type_from = as_fill_type(fill_type_from)
283
+ fill_type_to = as_fill_type(fill_type_to)
284
+
285
+ check_filled(filled, fill_type_from)
286
+
287
+ if fill_type_from == FillType.OuterCode:
288
+ if TYPE_CHECKING:
289
+ filled = cast(cpy.FillReturn_OuterCode, filled)
290
+ return _convert_filled_from_OuterCode(filled, fill_type_to)
291
+ elif fill_type_from == FillType.OuterOffset:
292
+ if TYPE_CHECKING:
293
+ filled = cast(cpy.FillReturn_OuterOffset, filled)
294
+ return _convert_filled_from_OuterOffset(filled, fill_type_to)
295
+ elif fill_type_from == FillType.ChunkCombinedCode:
296
+ if TYPE_CHECKING:
297
+ filled = cast(cpy.FillReturn_ChunkCombinedCode, filled)
298
+ return _convert_filled_from_ChunkCombinedCode(filled, fill_type_to)
299
+ elif fill_type_from == FillType.ChunkCombinedOffset:
300
+ if TYPE_CHECKING:
301
+ filled = cast(cpy.FillReturn_ChunkCombinedOffset, filled)
302
+ return _convert_filled_from_ChunkCombinedOffset(filled, fill_type_to)
303
+ elif fill_type_from == FillType.ChunkCombinedCodeOffset:
304
+ if TYPE_CHECKING:
305
+ filled = cast(cpy.FillReturn_ChunkCombinedCodeOffset, filled)
306
+ return _convert_filled_from_ChunkCombinedCodeOffset(filled, fill_type_to)
307
+ elif fill_type_from == FillType.ChunkCombinedOffsetOffset:
308
+ if TYPE_CHECKING:
309
+ filled = cast(cpy.FillReturn_ChunkCombinedOffsetOffset, filled)
310
+ return _convert_filled_from_ChunkCombinedOffsetOffset(filled, fill_type_to)
311
+ else:
312
+ raise ValueError(f"Invalid FillType {fill_type_from}")
313
+
314
+
315
+ def _convert_lines_from_Separate(
316
+ lines: cpy.LineReturn_Separate,
317
+ line_type_to: LineType,
318
+ ) -> cpy.LineReturn:
319
+ if line_type_to == LineType.Separate:
320
+ return lines
321
+ elif line_type_to == LineType.SeparateCode:
322
+ separate_codes = [arr.codes_from_points(line) for line in lines]
323
+ return (lines, separate_codes)
324
+ elif line_type_to == LineType.ChunkCombinedCode:
325
+ if not lines:
326
+ ret1: cpy.LineReturn_ChunkCombinedCode = ([None], [None])
327
+ else:
328
+ points = arr.concat_points(lines)
329
+ offsets = arr.offsets_from_lengths(lines)
330
+ codes = arr.codes_from_offsets_and_points(offsets, points)
331
+ ret1 = ([points], [codes])
332
+ return ret1
333
+ elif line_type_to == LineType.ChunkCombinedOffset:
334
+ if not lines:
335
+ ret2: cpy.LineReturn_ChunkCombinedOffset = ([None], [None])
336
+ else:
337
+ ret2 = ([arr.concat_points(lines)], [arr.offsets_from_lengths(lines)])
338
+ return ret2
339
+ elif line_type_to == LineType.ChunkCombinedNan:
340
+ if not lines:
341
+ ret3: cpy.LineReturn_ChunkCombinedNan = ([None],)
342
+ else:
343
+ ret3 = ([arr.concat_points_with_nan(lines)],)
344
+ return ret3
345
+ else:
346
+ raise ValueError(f"Invalid LineType {line_type_to}")
347
+
348
+
349
+ def _convert_lines_from_SeparateCode(
350
+ lines: cpy.LineReturn_SeparateCode,
351
+ line_type_to: LineType,
352
+ ) -> cpy.LineReturn:
353
+ if line_type_to == LineType.Separate:
354
+ # Drop codes.
355
+ return lines[0]
356
+ elif line_type_to == LineType.SeparateCode:
357
+ return lines
358
+ elif line_type_to == LineType.ChunkCombinedCode:
359
+ if not lines[0]:
360
+ ret1: cpy.LineReturn_ChunkCombinedCode = ([None], [None])
361
+ else:
362
+ ret1 = ([arr.concat_points(lines[0])], [arr.concat_codes(lines[1])])
363
+ return ret1
364
+ elif line_type_to == LineType.ChunkCombinedOffset:
365
+ if not lines[0]:
366
+ ret2: cpy.LineReturn_ChunkCombinedOffset = ([None], [None])
367
+ else:
368
+ ret2 = ([arr.concat_points(lines[0])], [arr.offsets_from_lengths(lines[0])])
369
+ return ret2
370
+ elif line_type_to == LineType.ChunkCombinedNan:
371
+ if not lines[0]:
372
+ ret3: cpy.LineReturn_ChunkCombinedNan = ([None],)
373
+ else:
374
+ ret3 = ([arr.concat_points_with_nan(lines[0])],)
375
+ return ret3
376
+ else:
377
+ raise ValueError(f"Invalid LineType {line_type_to}")
378
+
379
+
380
+ def _convert_lines_from_ChunkCombinedCode(
381
+ lines: cpy.LineReturn_ChunkCombinedCode,
382
+ line_type_to: LineType,
383
+ ) -> cpy.LineReturn:
384
+ if line_type_to in (LineType.Separate, LineType.SeparateCode):
385
+ separate_lines = []
386
+ for points, codes in zip(*lines):
387
+ if points is not None:
388
+ if TYPE_CHECKING:
389
+ assert codes is not None
390
+ split_at = np.nonzero(codes == MOVETO)[0]
391
+ if len(split_at) > 1:
392
+ separate_lines += np.split(points, split_at[1:])
393
+ else:
394
+ separate_lines.append(points)
395
+ if line_type_to == LineType.Separate:
396
+ return separate_lines
397
+ else:
398
+ separate_codes = [arr.codes_from_points(line) for line in separate_lines]
399
+ return (separate_lines, separate_codes)
400
+ elif line_type_to == LineType.ChunkCombinedCode:
401
+ return lines
402
+ elif line_type_to == LineType.ChunkCombinedOffset:
403
+ chunk_offsets = [None if codes is None else arr.offsets_from_codes(codes)
404
+ for codes in lines[1]]
405
+ return (lines[0], chunk_offsets)
406
+ elif line_type_to == LineType.ChunkCombinedNan:
407
+ points_nan: list[cpy.PointArray | None] = []
408
+ for points, codes in zip(*lines):
409
+ if points is None:
410
+ points_nan.append(None)
411
+ else:
412
+ if TYPE_CHECKING:
413
+ assert codes is not None
414
+ offsets = arr.offsets_from_codes(codes)
415
+ points_nan.append(arr.insert_nan_at_offsets(points, offsets))
416
+ return (points_nan,)
417
+ else:
418
+ raise ValueError(f"Invalid LineType {line_type_to}")
419
+
420
+
421
+ def _convert_lines_from_ChunkCombinedOffset(
422
+ lines: cpy.LineReturn_ChunkCombinedOffset,
423
+ line_type_to: LineType,
424
+ ) -> cpy.LineReturn:
425
+ if line_type_to in (LineType.Separate, LineType.SeparateCode):
426
+ separate_lines = []
427
+ for points, offsets in zip(*lines):
428
+ if points is not None:
429
+ if TYPE_CHECKING:
430
+ assert offsets is not None
431
+ separate_lines += arr.split_points_by_offsets(points, offsets)
432
+ if line_type_to == LineType.Separate:
433
+ return separate_lines
434
+ else:
435
+ separate_codes = [arr.codes_from_points(line) for line in separate_lines]
436
+ return (separate_lines, separate_codes)
437
+ elif line_type_to == LineType.ChunkCombinedCode:
438
+ chunk_codes: list[cpy.CodeArray | None] = []
439
+ for points, offsets in zip(*lines):
440
+ if points is None:
441
+ chunk_codes.append(None)
442
+ else:
443
+ if TYPE_CHECKING:
444
+ assert offsets is not None
445
+ chunk_codes.append(arr.codes_from_offsets_and_points(offsets, points))
446
+ return (lines[0], chunk_codes)
447
+ elif line_type_to == LineType.ChunkCombinedOffset:
448
+ return lines
449
+ elif line_type_to == LineType.ChunkCombinedNan:
450
+ points_nan: list[cpy.PointArray | None] = []
451
+ for points, offsets in zip(*lines):
452
+ if points is None:
453
+ points_nan.append(None)
454
+ else:
455
+ if TYPE_CHECKING:
456
+ assert offsets is not None
457
+ points_nan.append(arr.insert_nan_at_offsets(points, offsets))
458
+ return (points_nan,)
459
+ else:
460
+ raise ValueError(f"Invalid LineType {line_type_to}")
461
+
462
+
463
+ def _convert_lines_from_ChunkCombinedNan(
464
+ lines: cpy.LineReturn_ChunkCombinedNan,
465
+ line_type_to: LineType,
466
+ ) -> cpy.LineReturn:
467
+ if line_type_to in (LineType.Separate, LineType.SeparateCode):
468
+ separate_lines = []
469
+ for points in lines[0]:
470
+ if points is not None:
471
+ separate_lines += arr.split_points_at_nan(points)
472
+ if line_type_to == LineType.Separate:
473
+ return separate_lines
474
+ else:
475
+ separate_codes = [arr.codes_from_points(points) for points in separate_lines]
476
+ return (separate_lines, separate_codes)
477
+ elif line_type_to == LineType.ChunkCombinedCode:
478
+ chunk_points: list[cpy.PointArray | None] = []
479
+ chunk_codes: list[cpy.CodeArray | None] = []
480
+ for points in lines[0]:
481
+ if points is None:
482
+ chunk_points.append(None)
483
+ chunk_codes.append(None)
484
+ else:
485
+ points, offsets = arr.remove_nan(points)
486
+ chunk_points.append(points)
487
+ chunk_codes.append(arr.codes_from_offsets_and_points(offsets, points))
488
+ return (chunk_points, chunk_codes)
489
+ elif line_type_to == LineType.ChunkCombinedOffset:
490
+ chunk_points = []
491
+ chunk_offsets: list[cpy.OffsetArray | None] = []
492
+ for points in lines[0]:
493
+ if points is None:
494
+ chunk_points.append(None)
495
+ chunk_offsets.append(None)
496
+ else:
497
+ points, offsets = arr.remove_nan(points)
498
+ chunk_points.append(points)
499
+ chunk_offsets.append(offsets)
500
+ return (chunk_points, chunk_offsets)
501
+ elif line_type_to == LineType.ChunkCombinedNan:
502
+ return lines
503
+ else:
504
+ raise ValueError(f"Invalid LineType {line_type_to}")
505
+
506
+
507
+ def convert_lines(
508
+ lines: cpy.LineReturn,
509
+ line_type_from: LineType | str,
510
+ line_type_to: LineType | str,
511
+ ) -> cpy.LineReturn:
512
+ """Convert contour lines from one :class:`~.LineType` to another.
513
+
514
+ Args:
515
+ lines (sequence of arrays): Contour lines to convert, such as those returned by
516
+ :meth:`.ContourGenerator.lines`.
517
+ line_type_from (LineType or str): :class:`~.LineType` to convert from as enum or
518
+ string equivalent.
519
+ line_type_to (LineType or str): :class:`~.LineType` to convert to as enum or string
520
+ equivalent.
521
+
522
+ Return:
523
+ Converted contour lines.
524
+
525
+ When converting non-chunked line types (``LineType.Separate`` or ``LineType.SeparateCode``) to
526
+ chunked ones (``LineType.ChunkCombinedCode``, ``LineType.ChunkCombinedOffset`` or
527
+ ``LineType.ChunkCombinedNan``), all lines are placed in the first chunk. When converting in the
528
+ other direction, all chunk information is discarded.
529
+
530
+ .. versionadded:: 1.2.0
531
+ """
532
+ line_type_from = as_line_type(line_type_from)
533
+ line_type_to = as_line_type(line_type_to)
534
+
535
+ check_lines(lines, line_type_from)
536
+
537
+ if line_type_from == LineType.Separate:
538
+ if TYPE_CHECKING:
539
+ lines = cast(cpy.LineReturn_Separate, lines)
540
+ return _convert_lines_from_Separate(lines, line_type_to)
541
+ elif line_type_from == LineType.SeparateCode:
542
+ if TYPE_CHECKING:
543
+ lines = cast(cpy.LineReturn_SeparateCode, lines)
544
+ return _convert_lines_from_SeparateCode(lines, line_type_to)
545
+ elif line_type_from == LineType.ChunkCombinedCode:
546
+ if TYPE_CHECKING:
547
+ lines = cast(cpy.LineReturn_ChunkCombinedCode, lines)
548
+ return _convert_lines_from_ChunkCombinedCode(lines, line_type_to)
549
+ elif line_type_from == LineType.ChunkCombinedOffset:
550
+ if TYPE_CHECKING:
551
+ lines = cast(cpy.LineReturn_ChunkCombinedOffset, lines)
552
+ return _convert_lines_from_ChunkCombinedOffset(lines, line_type_to)
553
+ elif line_type_from == LineType.ChunkCombinedNan:
554
+ if TYPE_CHECKING:
555
+ lines = cast(cpy.LineReturn_ChunkCombinedNan, lines)
556
+ return _convert_lines_from_ChunkCombinedNan(lines, line_type_to)
557
+ else:
558
+ raise ValueError(f"Invalid LineType {line_type_from}")
559
+
560
+
561
+ def convert_multi_filled(
562
+ multi_filled: list[cpy.FillReturn],
563
+ fill_type_from: FillType | str,
564
+ fill_type_to: FillType | str,
565
+ ) -> list[cpy.FillReturn]:
566
+ """Convert multiple sets of filled contours from one :class:`~.FillType` to another.
567
+
568
+ Args:
569
+ multi_filled (nested sequence of arrays): Filled contour polygons to convert, such as those
570
+ returned by :meth:`.ContourGenerator.multi_filled`.
571
+ fill_type_from (FillType or str): :class:`~.FillType` to convert from as enum or
572
+ string equivalent.
573
+ fill_type_to (FillType or str): :class:`~.FillType` to convert to as enum or string
574
+ equivalent.
575
+
576
+ Return:
577
+ Converted sets filled contour polygons.
578
+
579
+ When converting non-chunked fill types (``FillType.OuterCode`` or ``FillType.OuterOffset``) to
580
+ chunked ones, all polygons are placed in the first chunk. When converting in the other
581
+ direction, all chunk information is discarded. Converting a fill type that is not aware of the
582
+ relationship between outer boundaries and contained holes (``FillType.ChunkCombinedCode`` or
583
+ ``FillType.ChunkCombinedOffset``) to one that is will raise a ``ValueError``.
584
+
585
+ .. versionadded:: 1.3.0
586
+ """
587
+ fill_type_from = as_fill_type(fill_type_from)
588
+ fill_type_to = as_fill_type(fill_type_to)
589
+
590
+ return [convert_filled(filled, fill_type_from, fill_type_to) for filled in multi_filled]
591
+
592
+
593
+ def convert_multi_lines(
594
+ multi_lines: list[cpy.LineReturn],
595
+ line_type_from: LineType | str,
596
+ line_type_to: LineType | str,
597
+ ) -> list[cpy.LineReturn]:
598
+ """Convert multiple sets of contour lines from one :class:`~.LineType` to another.
599
+
600
+ Args:
601
+ multi_lines (nested sequence of arrays): Contour lines to convert, such as those returned by
602
+ :meth:`.ContourGenerator.multi_lines`.
603
+ line_type_from (LineType or str): :class:`~.LineType` to convert from as enum or
604
+ string equivalent.
605
+ line_type_to (LineType or str): :class:`~.LineType` to convert to as enum or string
606
+ equivalent.
607
+
608
+ Return:
609
+ Converted set of contour lines.
610
+
611
+ When converting non-chunked line types (``LineType.Separate`` or ``LineType.SeparateCode``) to
612
+ chunked ones (``LineType.ChunkCombinedCode``, ``LineType.ChunkCombinedOffset`` or
613
+ ``LineType.ChunkCombinedNan``), all lines are placed in the first chunk. When converting in the
614
+ other direction, all chunk information is discarded.
615
+
616
+ .. versionadded:: 1.3.0
617
+ """
618
+ line_type_from = as_line_type(line_type_from)
619
+ line_type_to = as_line_type(line_type_to)
620
+
621
+ return [convert_lines(lines, line_type_from, line_type_to) for lines in multi_lines]
lib/python3.12/site-packages/contourpy/dechunk.py ADDED
@@ -0,0 +1,207 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING, cast
4
+
5
+ from contourpy._contourpy import FillType, LineType
6
+ from contourpy.array import (
7
+ concat_codes_or_none,
8
+ concat_offsets_or_none,
9
+ concat_points_or_none,
10
+ concat_points_or_none_with_nan,
11
+ )
12
+ from contourpy.enum_util import as_fill_type, as_line_type
13
+ from contourpy.typecheck import check_filled, check_lines
14
+
15
+ if TYPE_CHECKING:
16
+ import contourpy._contourpy as cpy
17
+
18
+
19
+ def dechunk_filled(filled: cpy.FillReturn, fill_type: FillType | str) -> cpy.FillReturn:
20
+ """Return the specified filled contours with chunked data moved into the first chunk.
21
+
22
+ Filled contours that are not chunked (``FillType.OuterCode`` and ``FillType.OuterOffset``) and
23
+ those that are but only contain a single chunk are returned unmodified. Individual polygons are
24
+ unchanged, they are not geometrically combined.
25
+
26
+ Args:
27
+ filled (sequence of arrays): Filled contour data, such as returned by
28
+ :meth:`.ContourGenerator.filled`.
29
+ fill_type (FillType or str): Type of :meth:`~.ContourGenerator.filled` as enum or string
30
+ equivalent.
31
+
32
+ Return:
33
+ Filled contours in a single chunk.
34
+
35
+ .. versionadded:: 1.2.0
36
+ """
37
+ fill_type = as_fill_type(fill_type)
38
+
39
+ if fill_type in (FillType.OuterCode, FillType.OuterOffset):
40
+ # No-op if fill_type is not chunked.
41
+ return filled
42
+
43
+ check_filled(filled, fill_type)
44
+ if len(filled[0]) < 2:
45
+ # No-op if just one chunk.
46
+ return filled
47
+
48
+ if TYPE_CHECKING:
49
+ filled = cast(cpy.FillReturn_Chunk, filled)
50
+ points = concat_points_or_none(filled[0])
51
+
52
+ if fill_type == FillType.ChunkCombinedCode:
53
+ if TYPE_CHECKING:
54
+ filled = cast(cpy.FillReturn_ChunkCombinedCode, filled)
55
+ if points is None:
56
+ ret1: cpy.FillReturn_ChunkCombinedCode = ([None], [None])
57
+ else:
58
+ ret1 = ([points], [concat_codes_or_none(filled[1])])
59
+ return ret1
60
+ elif fill_type == FillType.ChunkCombinedOffset:
61
+ if TYPE_CHECKING:
62
+ filled = cast(cpy.FillReturn_ChunkCombinedOffset, filled)
63
+ if points is None:
64
+ ret2: cpy.FillReturn_ChunkCombinedOffset = ([None], [None])
65
+ else:
66
+ ret2 = ([points], [concat_offsets_or_none(filled[1])])
67
+ return ret2
68
+ elif fill_type == FillType.ChunkCombinedCodeOffset:
69
+ if TYPE_CHECKING:
70
+ filled = cast(cpy.FillReturn_ChunkCombinedCodeOffset, filled)
71
+ if points is None:
72
+ ret3: cpy.FillReturn_ChunkCombinedCodeOffset = ([None], [None], [None])
73
+ else:
74
+ outer_offsets = concat_offsets_or_none(filled[2])
75
+ ret3 = ([points], [concat_codes_or_none(filled[1])], [outer_offsets])
76
+ return ret3
77
+ elif fill_type == FillType.ChunkCombinedOffsetOffset:
78
+ if TYPE_CHECKING:
79
+ filled = cast(cpy.FillReturn_ChunkCombinedOffsetOffset, filled)
80
+ if points is None:
81
+ ret4: cpy.FillReturn_ChunkCombinedOffsetOffset = ([None], [None], [None])
82
+ else:
83
+ outer_offsets = concat_offsets_or_none(filled[2])
84
+ ret4 = ([points], [concat_offsets_or_none(filled[1])], [outer_offsets])
85
+ return ret4
86
+ else:
87
+ raise ValueError(f"Invalid FillType {fill_type}")
88
+
89
+
90
+ def dechunk_lines(lines: cpy.LineReturn, line_type: LineType | str) -> cpy.LineReturn:
91
+ """Return the specified contour lines with chunked data moved into the first chunk.
92
+
93
+ Contour lines that are not chunked (``LineType.Separate`` and ``LineType.SeparateCode``) and
94
+ those that are but only contain a single chunk are returned unmodified. Individual lines are
95
+ unchanged, they are not geometrically combined.
96
+
97
+ Args:
98
+ lines (sequence of arrays): Contour line data, such as returned by
99
+ :meth:`.ContourGenerator.lines`.
100
+ line_type (LineType or str): Type of :meth:`~.ContourGenerator.lines` as enum or string
101
+ equivalent.
102
+
103
+ Return:
104
+ Contour lines in a single chunk.
105
+
106
+ .. versionadded:: 1.2.0
107
+ """
108
+ line_type = as_line_type(line_type)
109
+
110
+ if line_type in (LineType.Separate, LineType.SeparateCode):
111
+ # No-op if line_type is not chunked.
112
+ return lines
113
+
114
+ check_lines(lines, line_type)
115
+ if len(lines[0]) < 2:
116
+ # No-op if just one chunk.
117
+ return lines
118
+
119
+ if TYPE_CHECKING:
120
+ lines = cast(cpy.LineReturn_Chunk, lines)
121
+
122
+ if line_type == LineType.ChunkCombinedCode:
123
+ if TYPE_CHECKING:
124
+ lines = cast(cpy.LineReturn_ChunkCombinedCode, lines)
125
+ points = concat_points_or_none(lines[0])
126
+ if points is None:
127
+ ret1: cpy.LineReturn_ChunkCombinedCode = ([None], [None])
128
+ else:
129
+ ret1 = ([points], [concat_codes_or_none(lines[1])])
130
+ return ret1
131
+ elif line_type == LineType.ChunkCombinedOffset:
132
+ if TYPE_CHECKING:
133
+ lines = cast(cpy.LineReturn_ChunkCombinedOffset, lines)
134
+ points = concat_points_or_none(lines[0])
135
+ if points is None:
136
+ ret2: cpy.LineReturn_ChunkCombinedOffset = ([None], [None])
137
+ else:
138
+ ret2 = ([points], [concat_offsets_or_none(lines[1])])
139
+ return ret2
140
+ elif line_type == LineType.ChunkCombinedNan:
141
+ if TYPE_CHECKING:
142
+ lines = cast(cpy.LineReturn_ChunkCombinedNan, lines)
143
+ points = concat_points_or_none_with_nan(lines[0])
144
+ ret3: cpy.LineReturn_ChunkCombinedNan = ([points],)
145
+ return ret3
146
+ else:
147
+ raise ValueError(f"Invalid LineType {line_type}")
148
+
149
+
150
+ def dechunk_multi_filled(
151
+ multi_filled: list[cpy.FillReturn],
152
+ fill_type: FillType | str,
153
+ ) -> list[cpy.FillReturn]:
154
+ """Return multiple sets of filled contours with chunked data moved into the first chunks.
155
+
156
+ Filled contours that are not chunked (``FillType.OuterCode`` and ``FillType.OuterOffset``) and
157
+ those that are but only contain a single chunk are returned unmodified. Individual polygons are
158
+ unchanged, they are not geometrically combined.
159
+
160
+ Args:
161
+ multi_filled (nested sequence of arrays): Filled contour data, such as returned by
162
+ :meth:`.ContourGenerator.multi_filled`.
163
+ fill_type (FillType or str): Type of :meth:`~.ContourGenerator.filled` as enum or string
164
+ equivalent.
165
+
166
+ Return:
167
+ Multiple sets of filled contours in a single chunk.
168
+
169
+ .. versionadded:: 1.3.0
170
+ """
171
+ fill_type = as_fill_type(fill_type)
172
+
173
+ if fill_type in (FillType.OuterCode, FillType.OuterOffset):
174
+ # No-op if fill_type is not chunked.
175
+ return multi_filled
176
+
177
+ return [dechunk_filled(filled, fill_type) for filled in multi_filled]
178
+
179
+
180
+ def dechunk_multi_lines(
181
+ multi_lines: list[cpy.LineReturn],
182
+ line_type: LineType | str,
183
+ ) -> list[cpy.LineReturn]:
184
+ """Return multiple sets of contour lines with all chunked data moved into the first chunks.
185
+
186
+ Contour lines that are not chunked (``LineType.Separate`` and ``LineType.SeparateCode``) and
187
+ those that are but only contain a single chunk are returned unmodified. Individual lines are
188
+ unchanged, they are not geometrically combined.
189
+
190
+ Args:
191
+ multi_lines (nested sequence of arrays): Contour line data, such as returned by
192
+ :meth:`.ContourGenerator.multi_lines`.
193
+ line_type (LineType or str): Type of :meth:`~.ContourGenerator.lines` as enum or string
194
+ equivalent.
195
+
196
+ Return:
197
+ Multiple sets of contour lines in a single chunk.
198
+
199
+ .. versionadded:: 1.3.0
200
+ """
201
+ line_type = as_line_type(line_type)
202
+
203
+ if line_type in (LineType.Separate, LineType.SeparateCode):
204
+ # No-op if line_type is not chunked.
205
+ return multi_lines
206
+
207
+ return [dechunk_lines(lines, line_type) for lines in multi_lines]
lib/python3.12/site-packages/contourpy/enum_util.py ADDED
@@ -0,0 +1,57 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ from contourpy._contourpy import FillType, LineType, ZInterp
4
+
5
+
6
+ def as_fill_type(fill_type: FillType | str) -> FillType:
7
+ """Coerce a FillType or string value to a FillType.
8
+
9
+ Args:
10
+ fill_type (FillType or str): Value to convert.
11
+
12
+ Return:
13
+ FillType: Converted value.
14
+ """
15
+ if isinstance(fill_type, str):
16
+ try:
17
+ return FillType.__members__[fill_type]
18
+ except KeyError as e:
19
+ raise ValueError(f"'{fill_type}' is not a valid FillType") from e
20
+ else:
21
+ return fill_type
22
+
23
+
24
+ def as_line_type(line_type: LineType | str) -> LineType:
25
+ """Coerce a LineType or string value to a LineType.
26
+
27
+ Args:
28
+ line_type (LineType or str): Value to convert.
29
+
30
+ Return:
31
+ LineType: Converted value.
32
+ """
33
+ if isinstance(line_type, str):
34
+ try:
35
+ return LineType.__members__[line_type]
36
+ except KeyError as e:
37
+ raise ValueError(f"'{line_type}' is not a valid LineType") from e
38
+ else:
39
+ return line_type
40
+
41
+
42
+ def as_z_interp(z_interp: ZInterp | str) -> ZInterp:
43
+ """Coerce a ZInterp or string value to a ZInterp.
44
+
45
+ Args:
46
+ z_interp (ZInterp or str): Value to convert.
47
+
48
+ Return:
49
+ ZInterp: Converted value.
50
+ """
51
+ if isinstance(z_interp, str):
52
+ try:
53
+ return ZInterp.__members__[z_interp]
54
+ except KeyError as e:
55
+ raise ValueError(f"'{z_interp}' is not a valid ZInterp") from e
56
+ else:
57
+ return z_interp
lib/python3.12/site-packages/contourpy/py.typed ADDED
File without changes