Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/click/utils.py: 29%
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1from __future__ import annotations
3import collections.abc as cabc
4import os
5import re
6import sys
7import typing as t
8from functools import update_wrapper
9from gettext import gettext as _
10from types import ModuleType
11from types import TracebackType
13from ._compat import _default_text_stderr
14from ._compat import _default_text_stdout
15from ._compat import _find_binary_writer
16from ._compat import binary_streams
17from ._compat import open_stream
18from ._compat import should_strip_ansi
19from ._compat import strip_ansi
20from ._compat import text_streams
21from ._compat import WIN
22from .globals import resolve_color_default
24if t.TYPE_CHECKING:
25 import typing_extensions as te
27 P = te.ParamSpec("P")
29R = t.TypeVar("R")
32def _posixify(name: str) -> str:
33 return "-".join(name.split()).lower()
36def _safecall(func: t.Callable[P, R]) -> t.Callable[P, R | None]:
37 """Wraps a function so that it swallows exceptions.
39 :meta private:
40 """
42 def wrapper(*args: P.args, **kwargs: P.kwargs) -> R | None:
43 try:
44 return func(*args, **kwargs)
45 except Exception:
46 pass
47 return None
49 return update_wrapper(wrapper, func)
52def make_str(value: t.Any) -> str:
53 """Converts a value into a valid string."""
54 if isinstance(value, bytes):
55 try:
56 return value.decode(sys.getfilesystemencoding())
57 except UnicodeError:
58 return value.decode("utf-8", "replace")
59 return str(value)
62def _make_default_short_help(help: str, max_length: int = 45) -> str:
63 """Returns a condensed version of help string.
65 :meta private:
66 """
67 # Consider only the first paragraph and collapse newlines, tabs and spaces.
68 words = help.partition("\n\n")[0].split()
70 # The first paragraph started with a "no rewrap" marker, ignore it.
71 if words and words[0] == "\b":
72 words = words[1:]
74 if not words:
75 return ""
77 last_index = len(words) - 1
79 # A period ends a sentence when it closes the text, or when the next word
80 # does not start in lowercase. A lowercase word continues the sentence, so
81 # the period belongs to an abbreviation such as "vs.".
82 for i, word in enumerate(words):
83 if word.endswith(".") and (i == last_index or not words[i + 1][0].islower()):
84 words = words[: i + 1]
85 break
87 text = " ".join(words)
89 if len(text) <= max_length:
90 return text
92 # The suffix alone does not fit, and shorten() rejects such a width.
93 if max_length < len("..."):
94 return "..."
96 # Imported late to keep the import footprint small.
97 import textwrap
99 # Do not split hyphenated words.
100 return textwrap.shorten(text, max_length, placeholder="...", break_on_hyphens=False)
103class _LazyFile:
104 """A lazy file works like a regular file but it does not fully open
105 the file but it does perform some basic checks early to see if the
106 filename parameter does make sense. This is useful for safely opening
107 files for writing.
109 :meta private:
110 """
112 name: str
113 mode: str
114 encoding: str | None
115 errors: str | None
116 atomic: bool
117 _f: t.IO[t.Any] | None
118 should_close: bool
120 def __init__(
121 self,
122 filename: str | os.PathLike[str],
123 mode: str = "r",
124 encoding: str | None = None,
125 errors: str | None = "strict",
126 atomic: bool = False,
127 ) -> None:
128 self.name = os.fspath(filename)
129 self.mode = mode
130 self.encoding = encoding
131 self.errors = errors
132 self.atomic = atomic
134 if self.name == "-":
135 self._f, self.should_close = open_stream(filename, mode, encoding, errors)
136 else:
137 if "r" in mode:
138 # Open and close the file in case we're opening it for
139 # reading so that we can catch at least some errors in
140 # some cases early.
141 open(filename, mode).close()
142 self._f = None
143 self.should_close = True
145 def __getattr__(self, name: str) -> t.Any:
146 return getattr(self.open(), name)
148 def __repr__(self) -> str:
149 if self._f is not None:
150 return repr(self._f)
151 return f"<unopened file '{format_filename(self.name)}' {self.mode}>"
153 def open(self) -> t.IO[t.Any]:
154 """Opens the file if it's not yet open. This call might fail with
155 a :exc:`FileError`. Not handling this error will produce an error
156 that Click shows.
157 """
158 if self._f is not None:
159 return self._f
160 try:
161 rv, self.should_close = open_stream(
162 self.name, self.mode, self.encoding, self.errors, atomic=self.atomic
163 )
164 except OSError as e:
165 from .exceptions import FileError
167 raise FileError(self.name, hint=e.strerror) from e
168 self._f = rv
169 return rv
171 def close(self) -> None:
172 """Closes the underlying file, no matter what."""
173 if self._f is not None:
174 self._f.close()
176 def close_intelligently(self) -> None:
177 """This function only closes the file if it was opened by the lazy
178 file wrapper. For instance this will never close stdin.
179 """
180 if self.should_close:
181 self.close()
183 def __enter__(self) -> _LazyFile:
184 return self
186 def __exit__(
187 self,
188 exc_type: type[BaseException] | None,
189 exc_value: BaseException | None,
190 tb: TracebackType | None,
191 ) -> None:
192 self.close_intelligently()
194 def __iter__(self) -> cabc.Iterator[t.AnyStr]:
195 self.open()
196 return iter(self._f) # type: ignore
199class _KeepOpenFile:
200 """Proxy a file object but keep it open across a ``with`` block.
202 Wraps a borrowed file (such as ``sys.stdin`` or ``sys.stdout``) so that
203 leaving a ``with`` block does not close it, as used by :func:`open_file`
204 for the ``-`` filename. The caller stays responsible for the file: an
205 explicit :meth:`close` still passes through to the wrapped object.
207 Dunder methods are proxied explicitly: implicit special-method lookups
208 bypass :meth:`__getattr__`, because Python resolves them on the type rather
209 than the instance.
211 :meta private:
212 """
214 _file: t.IO[t.Any]
216 def __init__(self, file: t.IO[t.Any]) -> None:
217 self._file = file
219 def __getattr__(self, name: str) -> t.Any:
220 return getattr(self._file, name)
222 def __enter__(self) -> _KeepOpenFile:
223 return self
225 def __exit__(
226 self,
227 exc_type: type[BaseException] | None,
228 exc_value: BaseException | None,
229 tb: TracebackType | None,
230 ) -> None:
231 pass
233 def __repr__(self) -> str:
234 return repr(self._file)
236 def __iter__(self) -> cabc.Iterator[t.AnyStr]:
237 return iter(self._file)
240def echo(
241 message: object = None,
242 file: t.IO[t.Any] | None = None,
243 nl: bool = True,
244 err: bool = False,
245 color: bool | None = None,
246) -> None:
247 """Print a message and newline to stdout or a file. This should be
248 used instead of :func:`print` because it provides better support
249 for different data, files, and environments.
251 Compared to :func:`print`, this does the following:
253 - Ensures that the output encoding is not misconfigured on Linux.
254 - Supports Unicode in the Windows console.
255 - Supports writing to binary outputs, and supports writing bytes
256 to text outputs.
257 - Removes ANSI color and style codes if the output does not look
258 like an interactive terminal.
259 - Always flushes the output.
261 :param message: The string or bytes to output. Other objects are
262 converted to strings.
263 :param file: The file to write to. Defaults to ``stdout``.
264 :param err: Write to ``stderr`` instead of ``stdout``.
265 :param nl: Print a newline after the message. Enabled by default.
266 :param color: Force showing or hiding colors and other styles. By
267 default Click will remove color if the output does not look like
268 an interactive terminal.
270 .. versionchanged:: 8.5.0
271 Colorama is no longer used for color on Windows.
273 .. versionchanged:: 6.0
274 Support Unicode output on the Windows console. Click does not
275 modify ``sys.stdout``, so ``sys.stdout.write()`` and ``print()``
276 will still not support Unicode.
278 .. versionchanged:: 4.0
279 Added the ``color`` parameter.
281 .. versionadded:: 3.0
282 Added the ``err`` parameter.
284 .. versionchanged:: 2.0
285 Support colors on Windows if colorama is installed.
286 """
287 if file is None:
288 if err:
289 file = _default_text_stderr()
290 else:
291 file = _default_text_stdout()
293 # There are no standard streams attached to write to. For example,
294 # pythonw on Windows.
295 if file is None:
296 return
298 match message:
299 case str() | bytes() | bytearray():
300 out = message
301 case None:
302 out = ""
303 case _:
304 out = str(message)
306 if nl:
307 if isinstance(out, str):
308 out += "\n"
309 else:
310 out += b"\n"
312 if not out:
313 file.flush()
314 return
316 # If there is a message and the value looks like bytes, we manually
317 # need to find the binary stream and write the message in there.
318 # This is done separately so that most stream types will work as you
319 # would expect. Eg: you can write to StringIO for other cases.
320 if isinstance(out, (bytes, bytearray)):
321 binary_file = _find_binary_writer(file)
322 if binary_file is not None:
323 file.flush()
324 binary_file.write(out)
325 binary_file.flush()
326 return
328 # ANSI style code support. For no message or bytes, nothing happens.
329 # When outputting to a file instead of a terminal, strip codes.
330 elif should_strip_ansi(file, resolve_color_default(color)):
331 out = strip_ansi(out)
333 file.write(out) # type: ignore
334 file.flush()
337def _get_binary_stream(name: t.Literal["stdin", "stdout", "stderr"]) -> t.BinaryIO:
338 """Returns a system stream for byte processing.
340 .. deprecated:: 8.5.0
341 Will be removed in Click 9.0.
343 :param name: the name of the stream to open. Valid names are ``'stdin'``,
344 ``'stdout'`` and ``'stderr'``
346 :meta private:
347 """
348 opener = binary_streams.get(name)
349 if opener is None:
350 raise TypeError(_("Unknown standard stream '{name}'").format(name=name))
351 return opener()
354def _get_text_stream(
355 name: t.Literal["stdin", "stdout", "stderr"],
356 encoding: str | None = None,
357 errors: str | None = "strict",
358) -> t.TextIO:
359 """Returns a system stream for text processing.
361 .. deprecated:: 8.5.0
362 Will be removed in Click 9.0.
364 This usually returns a wrapped stream around a binary stream returned from
365 :func:`get_binary_stream` but it also can take shortcuts for already
366 correctly configured streams.
369 :param name: the name of the stream to open. Valid names are ``'stdin'``,
370 ``'stdout'`` and ``'stderr'``
371 :param encoding: overrides the detected default encoding.
372 :param errors: overrides the default error mode.
373 :meta private:
374 """
375 opener = text_streams.get(name)
376 if opener is None:
377 raise TypeError(_("Unknown standard stream '{name}'").format(name=name))
378 return opener(encoding, errors)
381def open_file(
382 filename: str | os.PathLike[str],
383 mode: str = "r",
384 encoding: str | None = None,
385 errors: str | None = "strict",
386 lazy: bool = False,
387 atomic: bool = False,
388) -> t.IO[t.Any]:
389 """Open a file, with extra behavior to handle ``'-'`` to indicate
390 a standard stream, lazy open on write, and atomic write. Similar to
391 the behavior of the :class:`~click.File` param type.
393 If ``'-'`` is given to open ``stdout`` or ``stdin``, the stream is
394 wrapped so that using it in a context manager will not close it.
395 This makes it possible to use the function without accidentally
396 closing a standard stream:
398 .. code-block:: python
400 with open_file(filename) as f:
401 ...
403 :param filename: The name or Path of the file to open, or ``'-'`` for
404 ``stdin``/``stdout``.
405 :param mode: The mode in which to open the file.
406 :param encoding: The encoding to decode or encode a file opened in
407 text mode.
408 :param errors: The error handling mode.
409 :param lazy: Wait to open the file until it is accessed. For read
410 mode, the file is temporarily opened to raise access errors
411 early, then closed until it is read again.
412 :param atomic: Write to a temporary file and replace the given file
413 on close.
415 .. versionadded:: 3.0
416 """
417 if lazy:
418 return t.cast(
419 "t.IO[t.Any]", _LazyFile(filename, mode, encoding, errors, atomic=atomic)
420 )
422 f, should_close = open_stream(filename, mode, encoding, errors, atomic=atomic)
424 if not should_close:
425 f = t.cast("t.IO[t.Any]", _KeepOpenFile(f))
427 return f
430def format_filename(
431 filename: str | bytes | os.PathLike[str] | os.PathLike[bytes],
432 shorten: bool = False,
433) -> str:
434 """Format a filename as a string for display. Ensures the filename can be
435 displayed by replacing any invalid bytes or surrogate escapes in the name
436 with the replacement character ``�``.
438 Invalid bytes or surrogate escapes will raise an error when written to a
439 stream with ``errors="strict"``. This will typically happen with ``stdout``
440 when the locale is something like ``en_GB.UTF-8``.
442 Many scenarios *are* safe to write surrogates though, due to PEP 538 and
443 PEP 540, including:
445 - Writing to ``stderr``, which uses ``errors="backslashreplace"``.
446 - The system has ``LANG=C.UTF-8``, ``C``, or ``POSIX``. Python opens
447 stdout and stderr with ``errors="surrogateescape"``.
448 - None of ``LANG/LC_*`` are set. Python assumes ``LANG=C.UTF-8``.
449 - Python is started in UTF-8 mode with ``PYTHONUTF8=1`` or ``-X utf8``.
450 Python opens stdout and stderr with ``errors="surrogateescape"``.
452 :param filename: formats a filename for UI display. This will also convert
453 the filename into unicode without failing.
454 :param shorten: this optionally shortens the filename to strip of the
455 path that leads up to it.
456 """
457 if shorten:
458 filename = os.path.basename(filename)
459 else:
460 filename = os.fspath(filename)
462 if isinstance(filename, bytes):
463 filename = filename.decode(sys.getfilesystemencoding(), "replace")
464 else:
465 filename = filename.encode("utf-8", "surrogateescape").decode(
466 "utf-8", "replace"
467 )
469 return filename
472def get_app_dir(app_name: str, roaming: bool = True, force_posix: bool = False) -> str:
473 r"""Returns the config folder for the application. The default behavior
474 is to return whatever is most appropriate for the operating system.
476 To give you an idea, for an app called ``"Foo Bar"``, something like
477 the following folders could be returned:
479 Mac OS X:
480 ``~/Library/Application Support/Foo Bar``
481 Mac OS X (POSIX):
482 ``~/.foo-bar``
483 Unix:
484 ``~/.config/foo-bar``
485 Unix (POSIX):
486 ``~/.foo-bar``
487 Windows (roaming):
488 ``C:\Users\<user>\AppData\Roaming\Foo Bar``
489 Windows (not roaming):
490 ``C:\Users\<user>\AppData\Local\Foo Bar``
492 .. versionadded:: 2.0
494 :param app_name: the application name. This should be properly capitalized
495 and can contain whitespace.
496 :param roaming: controls if the folder should be roaming or not on Windows.
497 Has no effect otherwise.
498 :param force_posix: if this is set to `True` then on any POSIX system the
499 folder will be stored in the home folder with a leading
500 dot instead of the XDG config home or darwin's
501 application support folder.
502 """
503 if WIN:
504 key = "APPDATA" if roaming else "LOCALAPPDATA"
505 folder = os.environ.get(key)
506 if folder is None:
507 folder = os.path.expanduser("~")
508 return os.path.join(folder, app_name)
509 if force_posix:
510 return os.path.join(os.path.expanduser(f"~/.{_posixify(app_name)}"))
511 if sys.platform == "darwin":
512 return os.path.join(
513 os.path.expanduser("~/Library/Application Support"), app_name
514 )
515 return os.path.join(
516 os.environ.get("XDG_CONFIG_HOME", os.path.expanduser("~/.config")),
517 _posixify(app_name),
518 )
521class _PacifyFlushWrapper:
522 """This wrapper is used to catch and suppress BrokenPipeErrors resulting
523 from ``.flush()`` being called on broken pipe during the shutdown/final-GC
524 of the Python interpreter. Notably ``.flush()`` is always called on
525 ``sys.stdout`` and ``sys.stderr``. So as to have minimal impact on any
526 other cleanup code, and the case where the underlying file is not a broken
527 pipe, all calls and attributes are proxied.
529 :meta private:
530 """
532 wrapped: t.IO[t.Any]
534 def __init__(self, wrapped: t.IO[t.Any]) -> None:
535 self.wrapped = wrapped
537 def flush(self) -> None:
538 try:
539 self.wrapped.flush()
540 except OSError as e:
541 import errno
543 if e.errno != errno.EPIPE:
544 raise
546 def __getattr__(self, attr: str) -> t.Any:
547 return getattr(self.wrapped, attr)
550def _detect_program_name(
551 path: str | None = None, _main: ModuleType | None = None
552) -> str:
553 """Determine the command used to run the program, for use in help
554 text. If a file or entry point was executed, the file name is
555 returned. If ``python -m`` was used to execute a module or package,
556 ``python -m name`` is returned.
558 This doesn't try to be too precise, the goal is to give a concise
559 name for help text. Files are only shown as their name without the
560 path. ``python`` is only shown for modules, and the full path to
561 ``sys.executable`` is not shown.
563 :param path: The Python file being executed. Python puts this in
564 ``sys.argv[0]``, which is used by default.
565 :param _main: The ``__main__`` module. This should only be passed
566 during internal testing.
568 .. versionadded:: 8.0
569 Based on command args detection in the Werkzeug reloader.
571 :meta private:
572 """
573 if _main is None:
574 _main = sys.modules["__main__"]
576 if not path:
577 path = sys.argv[0]
579 # The value of __package__ indicates how Python was called. It may
580 # not exist if a setuptools script is installed as an egg. It may be
581 # set incorrectly for entry points created with pip on Windows.
582 # It is set to "" inside a Shiv or PEX zipapp.
583 if getattr(_main, "__package__", None) in {None, ""} or (
584 os.name == "nt"
585 and _main.__package__ == ""
586 and not os.path.exists(path)
587 and os.path.exists(f"{path}.exe")
588 ):
589 # Executed a file, like "python app.py".
590 return os.path.basename(path)
592 # Executed a module, like "python -m example".
593 # Rewritten by Python from "-m script" to "/path/to/script.py".
594 # Need to look at main module to determine how it was executed.
595 py_module = t.cast(str, _main.__package__)
596 name = os.path.splitext(os.path.basename(path))[0]
598 # A submodule like "example.cli".
599 if name != "__main__":
600 py_module = f"{py_module}.{name}"
602 return f"python -m {py_module.lstrip('.')}"
605def _expand_args(
606 args: cabc.Iterable[str],
607 *,
608 user: bool = True,
609 env: bool = True,
610 glob_recursive: bool = True,
611) -> list[str]:
612 """Simulate Unix shell expansion with Python functions.
614 See :func:`glob.glob`, :func:`os.path.expanduser`, and
615 :func:`os.path.expandvars`.
617 This is intended for use on Windows, where the shell does not do any
618 expansion. It may not exactly match what a Unix shell would do.
620 :param args: List of command line arguments to expand.
621 :param user: Expand user home directory.
622 :param env: Expand environment variables.
623 :param glob_recursive: ``**`` matches directories recursively.
625 .. versionchanged:: 8.1
626 Invalid glob patterns are treated as empty expansions rather
627 than raising an error.
629 .. versionadded:: 8.0
631 :meta private:
632 """
633 from glob import glob
635 out = []
637 for arg in args:
638 if user:
639 arg = os.path.expanduser(arg)
641 if env:
642 arg = os.path.expandvars(arg)
644 try:
645 matches = glob(arg, recursive=glob_recursive)
646 except re.error:
647 matches = []
649 if not matches:
650 out.append(arg)
651 else:
652 out.extend(matches)
654 return out
657def __getattr__(name: str) -> object:
658 import warnings
660 if name in {
661 "LazyFile",
662 "KeepOpenFile",
663 "make_default_short_help",
664 "PacifyFlushWrapper",
665 "safecall",
666 "get_text_stream",
667 "get_binary_stream",
668 }:
669 warnings.warn(
670 f"'click.utils.{name}' is deprecated and will be removed in Click 9.0.",
671 DeprecationWarning,
672 stacklevel=2,
673 )
674 return globals()[f"_{name}"]
676 raise AttributeError(name)