]> crepu.dev Git - config.git/blob - djavu-asus/emacs/elpy/rpc-venv/lib/python3.11/site-packages/click/utils.py
d536434f0bd00cd6fd910c506f5b85a8e485b964
[config.git] / djavu-asus / emacs / elpy / rpc-venv / lib / python3.11 / site-packages / click / utils.py
1 import os
2 import re
3 import sys
4 import typing as t
5 from functools import update_wrapper
6 from types import ModuleType
7 from types import TracebackType
8
9 from ._compat import _default_text_stderr
10 from ._compat import _default_text_stdout
11 from ._compat import _find_binary_writer
12 from ._compat import auto_wrap_for_ansi
13 from ._compat import binary_streams
14 from ._compat import open_stream
15 from ._compat import should_strip_ansi
16 from ._compat import strip_ansi
17 from ._compat import text_streams
18 from ._compat import WIN
19 from .globals import resolve_color_default
20
21 if t.TYPE_CHECKING:
22 import typing_extensions as te
23
24 P = te.ParamSpec("P")
25
26 R = t.TypeVar("R")
27
28
29 def _posixify(name: str) -> str:
30 return "-".join(name.split()).lower()
31
32
33 def safecall(func: "t.Callable[P, R]") -> "t.Callable[P, t.Optional[R]]":
34 """Wraps a function so that it swallows exceptions."""
35
36 def wrapper(*args: "P.args", **kwargs: "P.kwargs") -> t.Optional[R]:
37 try:
38 return func(*args, **kwargs)
39 except Exception:
40 pass
41 return None
42
43 return update_wrapper(wrapper, func)
44
45
46 def make_str(value: t.Any) -> str:
47 """Converts a value into a valid string."""
48 if isinstance(value, bytes):
49 try:
50 return value.decode(sys.getfilesystemencoding())
51 except UnicodeError:
52 return value.decode("utf-8", "replace")
53 return str(value)
54
55
56 def make_default_short_help(help: str, max_length: int = 45) -> str:
57 """Returns a condensed version of help string."""
58 # Consider only the first paragraph.
59 paragraph_end = help.find("\n\n")
60
61 if paragraph_end != -1:
62 help = help[:paragraph_end]
63
64 # Collapse newlines, tabs, and spaces.
65 words = help.split()
66
67 if not words:
68 return ""
69
70 # The first paragraph started with a "no rewrap" marker, ignore it.
71 if words[0] == "\b":
72 words = words[1:]
73
74 total_length = 0
75 last_index = len(words) - 1
76
77 for i, word in enumerate(words):
78 total_length += len(word) + (i > 0)
79
80 if total_length > max_length: # too long, truncate
81 break
82
83 if word[-1] == ".": # sentence end, truncate without "..."
84 return " ".join(words[: i + 1])
85
86 if total_length == max_length and i != last_index:
87 break # not at sentence end, truncate with "..."
88 else:
89 return " ".join(words) # no truncation needed
90
91 # Account for the length of the suffix.
92 total_length += len("...")
93
94 # remove words until the length is short enough
95 while i > 0:
96 total_length -= len(words[i]) + (i > 0)
97
98 if total_length <= max_length:
99 break
100
101 i -= 1
102
103 return " ".join(words[:i]) + "..."
104
105
106 class LazyFile:
107 """A lazy file works like a regular file but it does not fully open
108 the file but it does perform some basic checks early to see if the
109 filename parameter does make sense. This is useful for safely opening
110 files for writing.
111 """
112
113 def __init__(
114 self,
115 filename: t.Union[str, "os.PathLike[str]"],
116 mode: str = "r",
117 encoding: t.Optional[str] = None,
118 errors: t.Optional[str] = "strict",
119 atomic: bool = False,
120 ):
121 self.name: str = os.fspath(filename)
122 self.mode = mode
123 self.encoding = encoding
124 self.errors = errors
125 self.atomic = atomic
126 self._f: t.Optional[t.IO[t.Any]]
127 self.should_close: bool
128
129 if self.name == "-":
130 self._f, self.should_close = open_stream(filename, mode, encoding, errors)
131 else:
132 if "r" in mode:
133 # Open and close the file in case we're opening it for
134 # reading so that we can catch at least some errors in
135 # some cases early.
136 open(filename, mode).close()
137 self._f = None
138 self.should_close = True
139
140 def __getattr__(self, name: str) -> t.Any:
141 return getattr(self.open(), name)
142
143 def __repr__(self) -> str:
144 if self._f is not None:
145 return repr(self._f)
146 return f"<unopened file '{format_filename(self.name)}' {self.mode}>"
147
148 def open(self) -> t.IO[t.Any]:
149 """Opens the file if it's not yet open. This call might fail with
150 a :exc:`FileError`. Not handling this error will produce an error
151 that Click shows.
152 """
153 if self._f is not None:
154 return self._f
155 try:
156 rv, self.should_close = open_stream(
157 self.name, self.mode, self.encoding, self.errors, atomic=self.atomic
158 )
159 except OSError as e: # noqa: E402
160 from .exceptions import FileError
161
162 raise FileError(self.name, hint=e.strerror) from e
163 self._f = rv
164 return rv
165
166 def close(self) -> None:
167 """Closes the underlying file, no matter what."""
168 if self._f is not None:
169 self._f.close()
170
171 def close_intelligently(self) -> None:
172 """This function only closes the file if it was opened by the lazy
173 file wrapper. For instance this will never close stdin.
174 """
175 if self.should_close:
176 self.close()
177
178 def __enter__(self) -> "LazyFile":
179 return self
180
181 def __exit__(
182 self,
183 exc_type: t.Optional[t.Type[BaseException]],
184 exc_value: t.Optional[BaseException],
185 tb: t.Optional[TracebackType],
186 ) -> None:
187 self.close_intelligently()
188
189 def __iter__(self) -> t.Iterator[t.AnyStr]:
190 self.open()
191 return iter(self._f) # type: ignore
192
193
194 class KeepOpenFile:
195 def __init__(self, file: t.IO[t.Any]) -> None:
196 self._file: t.IO[t.Any] = file
197
198 def __getattr__(self, name: str) -> t.Any:
199 return getattr(self._file, name)
200
201 def __enter__(self) -> "KeepOpenFile":
202 return self
203
204 def __exit__(
205 self,
206 exc_type: t.Optional[t.Type[BaseException]],
207 exc_value: t.Optional[BaseException],
208 tb: t.Optional[TracebackType],
209 ) -> None:
210 pass
211
212 def __repr__(self) -> str:
213 return repr(self._file)
214
215 def __iter__(self) -> t.Iterator[t.AnyStr]:
216 return iter(self._file)
217
218
219 def echo(
220 message: t.Optional[t.Any] = None,
221 file: t.Optional[t.IO[t.Any]] = None,
222 nl: bool = True,
223 err: bool = False,
224 color: t.Optional[bool] = None,
225 ) -> None:
226 """Print a message and newline to stdout or a file. This should be
227 used instead of :func:`print` because it provides better support
228 for different data, files, and environments.
229
230 Compared to :func:`print`, this does the following:
231
232 - Ensures that the output encoding is not misconfigured on Linux.
233 - Supports Unicode in the Windows console.
234 - Supports writing to binary outputs, and supports writing bytes
235 to text outputs.
236 - Supports colors and styles on Windows.
237 - Removes ANSI color and style codes if the output does not look
238 like an interactive terminal.
239 - Always flushes the output.
240
241 :param message: The string or bytes to output. Other objects are
242 converted to strings.
243 :param file: The file to write to. Defaults to ``stdout``.
244 :param err: Write to ``stderr`` instead of ``stdout``.
245 :param nl: Print a newline after the message. Enabled by default.
246 :param color: Force showing or hiding colors and other styles. By
247 default Click will remove color if the output does not look like
248 an interactive terminal.
249
250 .. versionchanged:: 6.0
251 Support Unicode output on the Windows console. Click does not
252 modify ``sys.stdout``, so ``sys.stdout.write()`` and ``print()``
253 will still not support Unicode.
254
255 .. versionchanged:: 4.0
256 Added the ``color`` parameter.
257
258 .. versionadded:: 3.0
259 Added the ``err`` parameter.
260
261 .. versionchanged:: 2.0
262 Support colors on Windows if colorama is installed.
263 """
264 if file is None:
265 if err:
266 file = _default_text_stderr()
267 else:
268 file = _default_text_stdout()
269
270 # There are no standard streams attached to write to. For example,
271 # pythonw on Windows.
272 if file is None:
273 return
274
275 # Convert non bytes/text into the native string type.
276 if message is not None and not isinstance(message, (str, bytes, bytearray)):
277 out: t.Optional[t.Union[str, bytes]] = str(message)
278 else:
279 out = message
280
281 if nl:
282 out = out or ""
283 if isinstance(out, str):
284 out += "\n"
285 else:
286 out += b"\n"
287
288 if not out:
289 file.flush()
290 return
291
292 # If there is a message and the value looks like bytes, we manually
293 # need to find the binary stream and write the message in there.
294 # This is done separately so that most stream types will work as you
295 # would expect. Eg: you can write to StringIO for other cases.
296 if isinstance(out, (bytes, bytearray)):
297 binary_file = _find_binary_writer(file)
298
299 if binary_file is not None:
300 file.flush()
301 binary_file.write(out)
302 binary_file.flush()
303 return
304
305 # ANSI style code support. For no message or bytes, nothing happens.
306 # When outputting to a file instead of a terminal, strip codes.
307 else:
308 color = resolve_color_default(color)
309
310 if should_strip_ansi(file, color):
311 out = strip_ansi(out)
312 elif WIN:
313 if auto_wrap_for_ansi is not None:
314 file = auto_wrap_for_ansi(file) # type: ignore
315 elif not color:
316 out = strip_ansi(out)
317
318 file.write(out) # type: ignore
319 file.flush()
320
321
322 def get_binary_stream(name: "te.Literal['stdin', 'stdout', 'stderr']") -> t.BinaryIO:
323 """Returns a system stream for byte processing.
324
325 :param name: the name of the stream to open. Valid names are ``'stdin'``,
326 ``'stdout'`` and ``'stderr'``
327 """
328 opener = binary_streams.get(name)
329 if opener is None:
330 raise TypeError(f"Unknown standard stream '{name}'")
331 return opener()
332
333
334 def get_text_stream(
335 name: "te.Literal['stdin', 'stdout', 'stderr']",
336 encoding: t.Optional[str] = None,
337 errors: t.Optional[str] = "strict",
338 ) -> t.TextIO:
339 """Returns a system stream for text processing. This usually returns
340 a wrapped stream around a binary stream returned from
341 :func:`get_binary_stream` but it also can take shortcuts for already
342 correctly configured streams.
343
344 :param name: the name of the stream to open. Valid names are ``'stdin'``,
345 ``'stdout'`` and ``'stderr'``
346 :param encoding: overrides the detected default encoding.
347 :param errors: overrides the default error mode.
348 """
349 opener = text_streams.get(name)
350 if opener is None:
351 raise TypeError(f"Unknown standard stream '{name}'")
352 return opener(encoding, errors)
353
354
355 def open_file(
356 filename: str,
357 mode: str = "r",
358 encoding: t.Optional[str] = None,
359 errors: t.Optional[str] = "strict",
360 lazy: bool = False,
361 atomic: bool = False,
362 ) -> t.IO[t.Any]:
363 """Open a file, with extra behavior to handle ``'-'`` to indicate
364 a standard stream, lazy open on write, and atomic write. Similar to
365 the behavior of the :class:`~click.File` param type.
366
367 If ``'-'`` is given to open ``stdout`` or ``stdin``, the stream is
368 wrapped so that using it in a context manager will not close it.
369 This makes it possible to use the function without accidentally
370 closing a standard stream:
371
372 .. code-block:: python
373
374 with open_file(filename) as f:
375 ...
376
377 :param filename: The name of the file to open, or ``'-'`` for
378 ``stdin``/``stdout``.
379 :param mode: The mode in which to open the file.
380 :param encoding: The encoding to decode or encode a file opened in
381 text mode.
382 :param errors: The error handling mode.
383 :param lazy: Wait to open the file until it is accessed. For read
384 mode, the file is temporarily opened to raise access errors
385 early, then closed until it is read again.
386 :param atomic: Write to a temporary file and replace the given file
387 on close.
388
389 .. versionadded:: 3.0
390 """
391 if lazy:
392 return t.cast(
393 t.IO[t.Any], LazyFile(filename, mode, encoding, errors, atomic=atomic)
394 )
395
396 f, should_close = open_stream(filename, mode, encoding, errors, atomic=atomic)
397
398 if not should_close:
399 f = t.cast(t.IO[t.Any], KeepOpenFile(f))
400
401 return f
402
403
404 def format_filename(
405 filename: "t.Union[str, bytes, os.PathLike[str], os.PathLike[bytes]]",
406 shorten: bool = False,
407 ) -> str:
408 """Format a filename as a string for display. Ensures the filename can be
409 displayed by replacing any invalid bytes or surrogate escapes in the name
410 with the replacement character ``�``.
411
412 Invalid bytes or surrogate escapes will raise an error when written to a
413 stream with ``errors="strict". This will typically happen with ``stdout``
414 when the locale is something like ``en_GB.UTF-8``.
415
416 Many scenarios *are* safe to write surrogates though, due to PEP 538 and
417 PEP 540, including:
418
419 - Writing to ``stderr``, which uses ``errors="backslashreplace"``.
420 - The system has ``LANG=C.UTF-8``, ``C``, or ``POSIX``. Python opens
421 stdout and stderr with ``errors="surrogateescape"``.
422 - None of ``LANG/LC_*`` are set. Python assumes ``LANG=C.UTF-8``.
423 - Python is started in UTF-8 mode with ``PYTHONUTF8=1`` or ``-X utf8``.
424 Python opens stdout and stderr with ``errors="surrogateescape"``.
425
426 :param filename: formats a filename for UI display. This will also convert
427 the filename into unicode without failing.
428 :param shorten: this optionally shortens the filename to strip of the
429 path that leads up to it.
430 """
431 if shorten:
432 filename = os.path.basename(filename)
433 else:
434 filename = os.fspath(filename)
435
436 if isinstance(filename, bytes):
437 filename = filename.decode(sys.getfilesystemencoding(), "replace")
438 else:
439 filename = filename.encode("utf-8", "surrogateescape").decode(
440 "utf-8", "replace"
441 )
442
443 return filename
444
445
446 def get_app_dir(app_name: str, roaming: bool = True, force_posix: bool = False) -> str:
447 r"""Returns the config folder for the application. The default behavior
448 is to return whatever is most appropriate for the operating system.
449
450 To give you an idea, for an app called ``"Foo Bar"``, something like
451 the following folders could be returned:
452
453 Mac OS X:
454 ``~/Library/Application Support/Foo Bar``
455 Mac OS X (POSIX):
456 ``~/.foo-bar``
457 Unix:
458 ``~/.config/foo-bar``
459 Unix (POSIX):
460 ``~/.foo-bar``
461 Windows (roaming):
462 ``C:\Users\<user>\AppData\Roaming\Foo Bar``
463 Windows (not roaming):
464 ``C:\Users\<user>\AppData\Local\Foo Bar``
465
466 .. versionadded:: 2.0
467
468 :param app_name: the application name. This should be properly capitalized
469 and can contain whitespace.
470 :param roaming: controls if the folder should be roaming or not on Windows.
471 Has no effect otherwise.
472 :param force_posix: if this is set to `True` then on any POSIX system the
473 folder will be stored in the home folder with a leading
474 dot instead of the XDG config home or darwin's
475 application support folder.
476 """
477 if WIN:
478 key = "APPDATA" if roaming else "LOCALAPPDATA"
479 folder = os.environ.get(key)
480 if folder is None:
481 folder = os.path.expanduser("~")
482 return os.path.join(folder, app_name)
483 if force_posix:
484 return os.path.join(os.path.expanduser(f"~/.{_posixify(app_name)}"))
485 if sys.platform == "darwin":
486 return os.path.join(
487 os.path.expanduser("~/Library/Application Support"), app_name
488 )
489 return os.path.join(
490 os.environ.get("XDG_CONFIG_HOME", os.path.expanduser("~/.config")),
491 _posixify(app_name),
492 )
493
494
495 class PacifyFlushWrapper:
496 """This wrapper is used to catch and suppress BrokenPipeErrors resulting
497 from ``.flush()`` being called on broken pipe during the shutdown/final-GC
498 of the Python interpreter. Notably ``.flush()`` is always called on
499 ``sys.stdout`` and ``sys.stderr``. So as to have minimal impact on any
500 other cleanup code, and the case where the underlying file is not a broken
501 pipe, all calls and attributes are proxied.
502 """
503
504 def __init__(self, wrapped: t.IO[t.Any]) -> None:
505 self.wrapped = wrapped
506
507 def flush(self) -> None:
508 try:
509 self.wrapped.flush()
510 except OSError as e:
511 import errno
512
513 if e.errno != errno.EPIPE:
514 raise
515
516 def __getattr__(self, attr: str) -> t.Any:
517 return getattr(self.wrapped, attr)
518
519
520 def _detect_program_name(
521 path: t.Optional[str] = None, _main: t.Optional[ModuleType] = None
522 ) -> str:
523 """Determine the command used to run the program, for use in help
524 text. If a file or entry point was executed, the file name is
525 returned. If ``python -m`` was used to execute a module or package,
526 ``python -m name`` is returned.
527
528 This doesn't try to be too precise, the goal is to give a concise
529 name for help text. Files are only shown as their name without the
530 path. ``python`` is only shown for modules, and the full path to
531 ``sys.executable`` is not shown.
532
533 :param path: The Python file being executed. Python puts this in
534 ``sys.argv[0]``, which is used by default.
535 :param _main: The ``__main__`` module. This should only be passed
536 during internal testing.
537
538 .. versionadded:: 8.0
539 Based on command args detection in the Werkzeug reloader.
540
541 :meta private:
542 """
543 if _main is None:
544 _main = sys.modules["__main__"]
545
546 if not path:
547 path = sys.argv[0]
548
549 # The value of __package__ indicates how Python was called. It may
550 # not exist if a setuptools script is installed as an egg. It may be
551 # set incorrectly for entry points created with pip on Windows.
552 # It is set to "" inside a Shiv or PEX zipapp.
553 if getattr(_main, "__package__", None) in {None, ""} or (
554 os.name == "nt"
555 and _main.__package__ == ""
556 and not os.path.exists(path)
557 and os.path.exists(f"{path}.exe")
558 ):
559 # Executed a file, like "python app.py".
560 return os.path.basename(path)
561
562 # Executed a module, like "python -m example".
563 # Rewritten by Python from "-m script" to "/path/to/script.py".
564 # Need to look at main module to determine how it was executed.
565 py_module = t.cast(str, _main.__package__)
566 name = os.path.splitext(os.path.basename(path))[0]
567
568 # A submodule like "example.cli".
569 if name != "__main__":
570 py_module = f"{py_module}.{name}"
571
572 return f"python -m {py_module.lstrip('.')}"
573
574
575 def _expand_args(
576 args: t.Iterable[str],
577 *,
578 user: bool = True,
579 env: bool = True,
580 glob_recursive: bool = True,
581 ) -> t.List[str]:
582 """Simulate Unix shell expansion with Python functions.
583
584 See :func:`glob.glob`, :func:`os.path.expanduser`, and
585 :func:`os.path.expandvars`.
586
587 This is intended for use on Windows, where the shell does not do any
588 expansion. It may not exactly match what a Unix shell would do.
589
590 :param args: List of command line arguments to expand.
591 :param user: Expand user home directory.
592 :param env: Expand environment variables.
593 :param glob_recursive: ``**`` matches directories recursively.
594
595 .. versionchanged:: 8.1
596 Invalid glob patterns are treated as empty expansions rather
597 than raising an error.
598
599 .. versionadded:: 8.0
600
601 :meta private:
602 """
603 from glob import glob
604
605 out = []
606
607 for arg in args:
608 if user:
609 arg = os.path.expanduser(arg)
610
611 if env:
612 arg = os.path.expandvars(arg)
613
614 try:
615 matches = glob(arg, recursive=glob_recursive)
616 except re.error:
617 matches = []
618
619 if not matches:
620 out.append(arg)
621 else:
622 out.extend(matches)
623
624 return out