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