Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/click/utils.py: 28%

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

247 statements  

1from __future__ import annotations 

2 

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 

12 

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 

23 

24if t.TYPE_CHECKING: 

25 import typing_extensions as te 

26 

27 P = te.ParamSpec("P") 

28 

29R = t.TypeVar("R") 

30 

31 

32def _posixify(name: str) -> str: 

33 return "-".join(name.split()).lower() 

34 

35 

36def _safecall(func: t.Callable[P, R]) -> t.Callable[P, R | None]: 

37 """Wraps a function so that it swallows exceptions. 

38 

39 :meta private: 

40 """ 

41 

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 

48 

49 return update_wrapper(wrapper, func) 

50 

51 

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) 

60 

61 

62def _make_default_short_help(help: str, max_length: int = 45) -> str: 

63 """Returns a condensed version of help string. 

64 

65 :meta private: 

66 """ 

67 # Consider only the first paragraph. 

68 paragraph_end = help.find("\n\n") 

69 

70 if paragraph_end != -1: 

71 help = help[:paragraph_end] 

72 

73 # Collapse newlines, tabs, and spaces. 

74 words = help.split() 

75 

76 if not words: 

77 return "" 

78 

79 # The first paragraph started with a "no rewrap" marker, ignore it. 

80 if words[0] == "\b": 

81 words = words[1:] 

82 

83 total_length = 0 

84 last_index = len(words) - 1 

85 

86 for i, word in enumerate(words): 

87 total_length += len(word) + (i > 0) 

88 

89 if total_length > max_length: # too long, truncate 

90 break 

91 

92 if word[-1] == ".": # sentence end, truncate without "..." 

93 return " ".join(words[: i + 1]) 

94 

95 if total_length == max_length and i != last_index: 

96 break # not at sentence end, truncate with "..." 

97 else: 

98 return " ".join(words) # no truncation needed 

99 

100 # Account for the length of the suffix. 

101 total_length += len("...") 

102 

103 # remove words until the length is short enough 

104 while i > 0: 

105 total_length -= len(words[i]) + (i > 0) 

106 

107 if total_length <= max_length: 

108 break 

109 

110 i -= 1 

111 

112 return " ".join(words[:i]) + "..." 

113 

114 

115class _LazyFile: 

116 """A lazy file works like a regular file but it does not fully open 

117 the file but it does perform some basic checks early to see if the 

118 filename parameter does make sense. This is useful for safely opening 

119 files for writing. 

120 

121 :meta private: 

122 """ 

123 

124 name: str 

125 mode: str 

126 encoding: str | None 

127 errors: str | None 

128 atomic: bool 

129 _f: t.IO[t.Any] | None 

130 should_close: bool 

131 

132 def __init__( 

133 self, 

134 filename: str | os.PathLike[str], 

135 mode: str = "r", 

136 encoding: str | None = None, 

137 errors: str | None = "strict", 

138 atomic: bool = False, 

139 ) -> None: 

140 self.name = os.fspath(filename) 

141 self.mode = mode 

142 self.encoding = encoding 

143 self.errors = errors 

144 self.atomic = atomic 

145 

146 if self.name == "-": 

147 self._f, self.should_close = open_stream(filename, mode, encoding, errors) 

148 else: 

149 if "r" in mode: 

150 # Open and close the file in case we're opening it for 

151 # reading so that we can catch at least some errors in 

152 # some cases early. 

153 open(filename, mode).close() 

154 self._f = None 

155 self.should_close = True 

156 

157 def __getattr__(self, name: str) -> t.Any: 

158 return getattr(self.open(), name) 

159 

160 def __repr__(self) -> str: 

161 if self._f is not None: 

162 return repr(self._f) 

163 return f"<unopened file '{format_filename(self.name)}' {self.mode}>" 

164 

165 def open(self) -> t.IO[t.Any]: 

166 """Opens the file if it's not yet open. This call might fail with 

167 a :exc:`FileError`. Not handling this error will produce an error 

168 that Click shows. 

169 """ 

170 if self._f is not None: 

171 return self._f 

172 try: 

173 rv, self.should_close = open_stream( 

174 self.name, self.mode, self.encoding, self.errors, atomic=self.atomic 

175 ) 

176 except OSError as e: 

177 from .exceptions import FileError 

178 

179 raise FileError(self.name, hint=e.strerror) from e 

180 self._f = rv 

181 return rv 

182 

183 def close(self) -> None: 

184 """Closes the underlying file, no matter what.""" 

185 if self._f is not None: 

186 self._f.close() 

187 

188 def close_intelligently(self) -> None: 

189 """This function only closes the file if it was opened by the lazy 

190 file wrapper. For instance this will never close stdin. 

191 """ 

192 if self.should_close: 

193 self.close() 

194 

195 def __enter__(self) -> _LazyFile: 

196 return self 

197 

198 def __exit__( 

199 self, 

200 exc_type: type[BaseException] | None, 

201 exc_value: BaseException | None, 

202 tb: TracebackType | None, 

203 ) -> None: 

204 self.close_intelligently() 

205 

206 def __iter__(self) -> cabc.Iterator[t.AnyStr]: 

207 self.open() 

208 return iter(self._f) # type: ignore 

209 

210 

211class _KeepOpenFile: 

212 """Proxy a file object but keep it open across a ``with`` block. 

213 

214 Wraps a borrowed file (such as ``sys.stdin`` or ``sys.stdout``) so that 

215 leaving a ``with`` block does not close it, as used by :func:`open_file` 

216 for the ``-`` filename. The caller stays responsible for the file: an 

217 explicit :meth:`close` still passes through to the wrapped object. 

218 

219 Dunder methods are proxied explicitly: implicit special-method lookups 

220 bypass :meth:`__getattr__`, because Python resolves them on the type rather 

221 than the instance. 

222 

223 :meta private: 

224 """ 

225 

226 _file: t.IO[t.Any] 

227 

228 def __init__(self, file: t.IO[t.Any]) -> None: 

229 self._file = file 

230 

231 def __getattr__(self, name: str) -> t.Any: 

232 return getattr(self._file, name) 

233 

234 def __enter__(self) -> _KeepOpenFile: 

235 return self 

236 

237 def __exit__( 

238 self, 

239 exc_type: type[BaseException] | None, 

240 exc_value: BaseException | None, 

241 tb: TracebackType | None, 

242 ) -> None: 

243 pass 

244 

245 def __repr__(self) -> str: 

246 return repr(self._file) 

247 

248 def __iter__(self) -> cabc.Iterator[t.AnyStr]: 

249 return iter(self._file) 

250 

251 

252def echo( 

253 message: object = None, 

254 file: t.IO[t.Any] | None = None, 

255 nl: bool = True, 

256 err: bool = False, 

257 color: bool | None = None, 

258) -> None: 

259 """Print a message and newline to stdout or a file. This should be 

260 used instead of :func:`print` because it provides better support 

261 for different data, files, and environments. 

262 

263 Compared to :func:`print`, this does the following: 

264 

265 - Ensures that the output encoding is not misconfigured on Linux. 

266 - Supports Unicode in the Windows console. 

267 - Supports writing to binary outputs, and supports writing bytes 

268 to text outputs. 

269 - Removes ANSI color and style codes if the output does not look 

270 like an interactive terminal. 

271 - Always flushes the output. 

272 

273 :param message: The string or bytes to output. Other objects are 

274 converted to strings. 

275 :param file: The file to write to. Defaults to ``stdout``. 

276 :param err: Write to ``stderr`` instead of ``stdout``. 

277 :param nl: Print a newline after the message. Enabled by default. 

278 :param color: Force showing or hiding colors and other styles. By 

279 default Click will remove color if the output does not look like 

280 an interactive terminal. 

281 

282 .. versionchanged:: 8.5.0 

283 Colorama is no longer used for color on Windows. 

284 

285 .. versionchanged:: 6.0 

286 Support Unicode output on the Windows console. Click does not 

287 modify ``sys.stdout``, so ``sys.stdout.write()`` and ``print()`` 

288 will still not support Unicode. 

289 

290 .. versionchanged:: 4.0 

291 Added the ``color`` parameter. 

292 

293 .. versionadded:: 3.0 

294 Added the ``err`` parameter. 

295 

296 .. versionchanged:: 2.0 

297 Support colors on Windows if colorama is installed. 

298 """ 

299 if file is None: 

300 if err: 

301 file = _default_text_stderr() 

302 else: 

303 file = _default_text_stdout() 

304 

305 # There are no standard streams attached to write to. For example, 

306 # pythonw on Windows. 

307 if file is None: 

308 return 

309 

310 match message: 

311 case str() | bytes() | bytearray(): 

312 out = message 

313 case None: 

314 out = "" 

315 case _: 

316 out = str(message) 

317 

318 if nl: 

319 if isinstance(out, str): 

320 out += "\n" 

321 else: 

322 out += b"\n" 

323 

324 if not out: 

325 file.flush() 

326 return 

327 

328 # If there is a message and the value looks like bytes, we manually 

329 # need to find the binary stream and write the message in there. 

330 # This is done separately so that most stream types will work as you 

331 # would expect. Eg: you can write to StringIO for other cases. 

332 if isinstance(out, (bytes, bytearray)): 

333 binary_file = _find_binary_writer(file) 

334 if binary_file is not None: 

335 file.flush() 

336 binary_file.write(out) 

337 binary_file.flush() 

338 return 

339 

340 # ANSI style code support. For no message or bytes, nothing happens. 

341 # When outputting to a file instead of a terminal, strip codes. 

342 elif should_strip_ansi(file, resolve_color_default(color)): 

343 out = strip_ansi(out) 

344 

345 file.write(out) # type: ignore 

346 file.flush() 

347 

348 

349def _get_binary_stream(name: t.Literal["stdin", "stdout", "stderr"]) -> t.BinaryIO: 

350 """Returns a system stream for byte processing. 

351 

352 .. deprecated:: 8.5.0 

353 Will be removed in Click 9.0. 

354 

355 :param name: the name of the stream to open. Valid names are ``'stdin'``, 

356 ``'stdout'`` and ``'stderr'`` 

357 

358 :meta private: 

359 """ 

360 opener = binary_streams.get(name) 

361 if opener is None: 

362 raise TypeError(_("Unknown standard stream '{name}'").format(name=name)) 

363 return opener() 

364 

365 

366def _get_text_stream( 

367 name: t.Literal["stdin", "stdout", "stderr"], 

368 encoding: str | None = None, 

369 errors: str | None = "strict", 

370) -> t.TextIO: 

371 """Returns a system stream for text processing. 

372 

373 .. deprecated:: 8.5.0 

374 Will be removed in Click 9.0. 

375 

376 This usually returns a wrapped stream around a binary stream returned from 

377 :func:`get_binary_stream` but it also can take shortcuts for already 

378 correctly configured streams. 

379 

380 

381 :param name: the name of the stream to open. Valid names are ``'stdin'``, 

382 ``'stdout'`` and ``'stderr'`` 

383 :param encoding: overrides the detected default encoding. 

384 :param errors: overrides the default error mode. 

385 :meta private: 

386 """ 

387 opener = text_streams.get(name) 

388 if opener is None: 

389 raise TypeError(_("Unknown standard stream '{name}'").format(name=name)) 

390 return opener(encoding, errors) 

391 

392 

393def open_file( 

394 filename: str | os.PathLike[str], 

395 mode: str = "r", 

396 encoding: str | None = None, 

397 errors: str | None = "strict", 

398 lazy: bool = False, 

399 atomic: bool = False, 

400) -> t.IO[t.Any]: 

401 """Open a file, with extra behavior to handle ``'-'`` to indicate 

402 a standard stream, lazy open on write, and atomic write. Similar to 

403 the behavior of the :class:`~click.File` param type. 

404 

405 If ``'-'`` is given to open ``stdout`` or ``stdin``, the stream is 

406 wrapped so that using it in a context manager will not close it. 

407 This makes it possible to use the function without accidentally 

408 closing a standard stream: 

409 

410 .. code-block:: python 

411 

412 with open_file(filename) as f: 

413 ... 

414 

415 :param filename: The name or Path of the file to open, or ``'-'`` for 

416 ``stdin``/``stdout``. 

417 :param mode: The mode in which to open the file. 

418 :param encoding: The encoding to decode or encode a file opened in 

419 text mode. 

420 :param errors: The error handling mode. 

421 :param lazy: Wait to open the file until it is accessed. For read 

422 mode, the file is temporarily opened to raise access errors 

423 early, then closed until it is read again. 

424 :param atomic: Write to a temporary file and replace the given file 

425 on close. 

426 

427 .. versionadded:: 3.0 

428 """ 

429 if lazy: 

430 return t.cast( 

431 "t.IO[t.Any]", _LazyFile(filename, mode, encoding, errors, atomic=atomic) 

432 ) 

433 

434 f, should_close = open_stream(filename, mode, encoding, errors, atomic=atomic) 

435 

436 if not should_close: 

437 f = t.cast("t.IO[t.Any]", _KeepOpenFile(f)) 

438 

439 return f 

440 

441 

442def format_filename( 

443 filename: str | bytes | os.PathLike[str] | os.PathLike[bytes], 

444 shorten: bool = False, 

445) -> str: 

446 """Format a filename as a string for display. Ensures the filename can be 

447 displayed by replacing any invalid bytes or surrogate escapes in the name 

448 with the replacement character ``�``. 

449 

450 Invalid bytes or surrogate escapes will raise an error when written to a 

451 stream with ``errors="strict"``. This will typically happen with ``stdout`` 

452 when the locale is something like ``en_GB.UTF-8``. 

453 

454 Many scenarios *are* safe to write surrogates though, due to PEP 538 and 

455 PEP 540, including: 

456 

457 - Writing to ``stderr``, which uses ``errors="backslashreplace"``. 

458 - The system has ``LANG=C.UTF-8``, ``C``, or ``POSIX``. Python opens 

459 stdout and stderr with ``errors="surrogateescape"``. 

460 - None of ``LANG/LC_*`` are set. Python assumes ``LANG=C.UTF-8``. 

461 - Python is started in UTF-8 mode with ``PYTHONUTF8=1`` or ``-X utf8``. 

462 Python opens stdout and stderr with ``errors="surrogateescape"``. 

463 

464 :param filename: formats a filename for UI display. This will also convert 

465 the filename into unicode without failing. 

466 :param shorten: this optionally shortens the filename to strip of the 

467 path that leads up to it. 

468 """ 

469 if shorten: 

470 filename = os.path.basename(filename) 

471 else: 

472 filename = os.fspath(filename) 

473 

474 if isinstance(filename, bytes): 

475 filename = filename.decode(sys.getfilesystemencoding(), "replace") 

476 else: 

477 filename = filename.encode("utf-8", "surrogateescape").decode( 

478 "utf-8", "replace" 

479 ) 

480 

481 return filename 

482 

483 

484def get_app_dir(app_name: str, roaming: bool = True, force_posix: bool = False) -> str: 

485 r"""Returns the config folder for the application. The default behavior 

486 is to return whatever is most appropriate for the operating system. 

487 

488 To give you an idea, for an app called ``"Foo Bar"``, something like 

489 the following folders could be returned: 

490 

491 Mac OS X: 

492 ``~/Library/Application Support/Foo Bar`` 

493 Mac OS X (POSIX): 

494 ``~/.foo-bar`` 

495 Unix: 

496 ``~/.config/foo-bar`` 

497 Unix (POSIX): 

498 ``~/.foo-bar`` 

499 Windows (roaming): 

500 ``C:\Users\<user>\AppData\Roaming\Foo Bar`` 

501 Windows (not roaming): 

502 ``C:\Users\<user>\AppData\Local\Foo Bar`` 

503 

504 .. versionadded:: 2.0 

505 

506 :param app_name: the application name. This should be properly capitalized 

507 and can contain whitespace. 

508 :param roaming: controls if the folder should be roaming or not on Windows. 

509 Has no effect otherwise. 

510 :param force_posix: if this is set to `True` then on any POSIX system the 

511 folder will be stored in the home folder with a leading 

512 dot instead of the XDG config home or darwin's 

513 application support folder. 

514 """ 

515 if WIN: 

516 key = "APPDATA" if roaming else "LOCALAPPDATA" 

517 folder = os.environ.get(key) 

518 if folder is None: 

519 folder = os.path.expanduser("~") 

520 return os.path.join(folder, app_name) 

521 if force_posix: 

522 return os.path.join(os.path.expanduser(f"~/.{_posixify(app_name)}")) 

523 if sys.platform == "darwin": 

524 return os.path.join( 

525 os.path.expanduser("~/Library/Application Support"), app_name 

526 ) 

527 return os.path.join( 

528 os.environ.get("XDG_CONFIG_HOME", os.path.expanduser("~/.config")), 

529 _posixify(app_name), 

530 ) 

531 

532 

533class _PacifyFlushWrapper: 

534 """This wrapper is used to catch and suppress BrokenPipeErrors resulting 

535 from ``.flush()`` being called on broken pipe during the shutdown/final-GC 

536 of the Python interpreter. Notably ``.flush()`` is always called on 

537 ``sys.stdout`` and ``sys.stderr``. So as to have minimal impact on any 

538 other cleanup code, and the case where the underlying file is not a broken 

539 pipe, all calls and attributes are proxied. 

540 

541 :meta private: 

542 """ 

543 

544 wrapped: t.IO[t.Any] 

545 

546 def __init__(self, wrapped: t.IO[t.Any]) -> None: 

547 self.wrapped = wrapped 

548 

549 def flush(self) -> None: 

550 try: 

551 self.wrapped.flush() 

552 except OSError as e: 

553 import errno 

554 

555 if e.errno != errno.EPIPE: 

556 raise 

557 

558 def __getattr__(self, attr: str) -> t.Any: 

559 return getattr(self.wrapped, attr) 

560 

561 

562def _detect_program_name( 

563 path: str | None = None, _main: ModuleType | None = None 

564) -> str: 

565 """Determine the command used to run the program, for use in help 

566 text. If a file or entry point was executed, the file name is 

567 returned. If ``python -m`` was used to execute a module or package, 

568 ``python -m name`` is returned. 

569 

570 This doesn't try to be too precise, the goal is to give a concise 

571 name for help text. Files are only shown as their name without the 

572 path. ``python`` is only shown for modules, and the full path to 

573 ``sys.executable`` is not shown. 

574 

575 :param path: The Python file being executed. Python puts this in 

576 ``sys.argv[0]``, which is used by default. 

577 :param _main: The ``__main__`` module. This should only be passed 

578 during internal testing. 

579 

580 .. versionadded:: 8.0 

581 Based on command args detection in the Werkzeug reloader. 

582 

583 :meta private: 

584 """ 

585 if _main is None: 

586 _main = sys.modules["__main__"] 

587 

588 if not path: 

589 path = sys.argv[0] 

590 

591 # The value of __package__ indicates how Python was called. It may 

592 # not exist if a setuptools script is installed as an egg. It may be 

593 # set incorrectly for entry points created with pip on Windows. 

594 # It is set to "" inside a Shiv or PEX zipapp. 

595 if getattr(_main, "__package__", None) in {None, ""} or ( 

596 os.name == "nt" 

597 and _main.__package__ == "" 

598 and not os.path.exists(path) 

599 and os.path.exists(f"{path}.exe") 

600 ): 

601 # Executed a file, like "python app.py". 

602 return os.path.basename(path) 

603 

604 # Executed a module, like "python -m example". 

605 # Rewritten by Python from "-m script" to "/path/to/script.py". 

606 # Need to look at main module to determine how it was executed. 

607 py_module = t.cast(str, _main.__package__) 

608 name = os.path.splitext(os.path.basename(path))[0] 

609 

610 # A submodule like "example.cli". 

611 if name != "__main__": 

612 py_module = f"{py_module}.{name}" 

613 

614 return f"python -m {py_module.lstrip('.')}" 

615 

616 

617def _expand_args( 

618 args: cabc.Iterable[str], 

619 *, 

620 user: bool = True, 

621 env: bool = True, 

622 glob_recursive: bool = True, 

623) -> list[str]: 

624 """Simulate Unix shell expansion with Python functions. 

625 

626 See :func:`glob.glob`, :func:`os.path.expanduser`, and 

627 :func:`os.path.expandvars`. 

628 

629 This is intended for use on Windows, where the shell does not do any 

630 expansion. It may not exactly match what a Unix shell would do. 

631 

632 :param args: List of command line arguments to expand. 

633 :param user: Expand user home directory. 

634 :param env: Expand environment variables. 

635 :param glob_recursive: ``**`` matches directories recursively. 

636 

637 .. versionchanged:: 8.1 

638 Invalid glob patterns are treated as empty expansions rather 

639 than raising an error. 

640 

641 .. versionadded:: 8.0 

642 

643 :meta private: 

644 """ 

645 from glob import glob 

646 

647 out = [] 

648 

649 for arg in args: 

650 if user: 

651 arg = os.path.expanduser(arg) 

652 

653 if env: 

654 arg = os.path.expandvars(arg) 

655 

656 try: 

657 matches = glob(arg, recursive=glob_recursive) 

658 except re.error: 

659 matches = [] 

660 

661 if not matches: 

662 out.append(arg) 

663 else: 

664 out.extend(matches) 

665 

666 return out 

667 

668 

669def __getattr__(name: str) -> object: 

670 import warnings 

671 

672 if name in { 

673 "LazyFile", 

674 "KeepOpenFile", 

675 "make_default_short_help", 

676 "PacifyFlushWrapper", 

677 "safecall", 

678 "get_text_stream", 

679 "get_binary_stream", 

680 }: 

681 warnings.warn( 

682 f"'click.utils.{name}' is deprecated and will be removed in Click 9.0.", 

683 DeprecationWarning, 

684 stacklevel=2, 

685 ) 

686 return globals()[f"_{name}"] 

687 

688 raise AttributeError(name)