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

238 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 and collapse newlines, tabs and spaces. 

68 words = help.partition("\n\n")[0].split() 

69 

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

71 if words and words[0] == "\b": 

72 words = words[1:] 

73 

74 if not words: 

75 return "" 

76 

77 last_index = len(words) - 1 

78 

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 

86 

87 text = " ".join(words) 

88 

89 if len(text) <= max_length: 

90 return text 

91 

92 # The suffix alone does not fit, and shorten() rejects such a width. 

93 if max_length < len("..."): 

94 return "..." 

95 

96 # Imported late to keep the import footprint small. 

97 import textwrap 

98 

99 # Do not split hyphenated words. 

100 return textwrap.shorten(text, max_length, placeholder="...", break_on_hyphens=False) 

101 

102 

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. 

108 

109 :meta private: 

110 """ 

111 

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 

119 

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 

133 

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 

144 

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

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

147 

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}>" 

152 

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 

166 

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

168 self._f = rv 

169 return rv 

170 

171 def close(self) -> None: 

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

173 if self._f is not None: 

174 self._f.close() 

175 

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() 

182 

183 def __enter__(self) -> _LazyFile: 

184 return self 

185 

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() 

193 

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

195 self.open() 

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

197 

198 

199class _KeepOpenFile: 

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

201 

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. 

206 

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. 

210 

211 :meta private: 

212 """ 

213 

214 _file: t.IO[t.Any] 

215 

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

217 self._file = file 

218 

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

220 return getattr(self._file, name) 

221 

222 def __enter__(self) -> _KeepOpenFile: 

223 return self 

224 

225 def __exit__( 

226 self, 

227 exc_type: type[BaseException] | None, 

228 exc_value: BaseException | None, 

229 tb: TracebackType | None, 

230 ) -> None: 

231 pass 

232 

233 def __repr__(self) -> str: 

234 return repr(self._file) 

235 

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

237 return iter(self._file) 

238 

239 

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. 

250 

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

252 

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. 

260 

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. 

269 

270 .. versionchanged:: 8.5.0 

271 Colorama is no longer used for color on Windows. 

272 

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. 

277 

278 .. versionchanged:: 4.0 

279 Added the ``color`` parameter. 

280 

281 .. versionadded:: 3.0 

282 Added the ``err`` parameter. 

283 

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() 

292 

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

294 # pythonw on Windows. 

295 if file is None: 

296 return 

297 

298 match message: 

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

300 out = message 

301 case None: 

302 out = "" 

303 case _: 

304 out = str(message) 

305 

306 if nl: 

307 if isinstance(out, str): 

308 out += "\n" 

309 else: 

310 out += b"\n" 

311 

312 if not out: 

313 file.flush() 

314 return 

315 

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 

327 

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) 

332 

333 file.write(out) # type: ignore 

334 file.flush() 

335 

336 

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

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

339 

340 .. deprecated:: 8.5.0 

341 Will be removed in Click 9.0. 

342 

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

344 ``'stdout'`` and ``'stderr'`` 

345 

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() 

352 

353 

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. 

360 

361 .. deprecated:: 8.5.0 

362 Will be removed in Click 9.0. 

363 

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. 

367 

368 

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) 

379 

380 

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. 

392 

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: 

397 

398 .. code-block:: python 

399 

400 with open_file(filename) as f: 

401 ... 

402 

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. 

414 

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 ) 

421 

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

423 

424 if not should_close: 

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

426 

427 return f 

428 

429 

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 ``�``. 

437 

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``. 

441 

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

443 PEP 540, including: 

444 

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"``. 

451 

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) 

461 

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 ) 

468 

469 return filename 

470 

471 

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. 

475 

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

477 the following folders could be returned: 

478 

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`` 

491 

492 .. versionadded:: 2.0 

493 

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 ) 

519 

520 

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. 

528 

529 :meta private: 

530 """ 

531 

532 wrapped: t.IO[t.Any] 

533 

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

535 self.wrapped = wrapped 

536 

537 def flush(self) -> None: 

538 try: 

539 self.wrapped.flush() 

540 except OSError as e: 

541 import errno 

542 

543 if e.errno != errno.EPIPE: 

544 raise 

545 

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

547 return getattr(self.wrapped, attr) 

548 

549 

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. 

557 

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. 

562 

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. 

567 

568 .. versionadded:: 8.0 

569 Based on command args detection in the Werkzeug reloader. 

570 

571 :meta private: 

572 """ 

573 if _main is None: 

574 _main = sys.modules["__main__"] 

575 

576 if not path: 

577 path = sys.argv[0] 

578 

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) 

591 

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] 

597 

598 # A submodule like "example.cli". 

599 if name != "__main__": 

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

601 

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

603 

604 

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. 

613 

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

615 :func:`os.path.expandvars`. 

616 

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. 

619 

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. 

624 

625 .. versionchanged:: 8.1 

626 Invalid glob patterns are treated as empty expansions rather 

627 than raising an error. 

628 

629 .. versionadded:: 8.0 

630 

631 :meta private: 

632 """ 

633 from glob import glob 

634 

635 out = [] 

636 

637 for arg in args: 

638 if user: 

639 arg = os.path.expanduser(arg) 

640 

641 if env: 

642 arg = os.path.expandvars(arg) 

643 

644 try: 

645 matches = glob(arg, recursive=glob_recursive) 

646 except re.error: 

647 matches = [] 

648 

649 if not matches: 

650 out.append(arg) 

651 else: 

652 out.extend(matches) 

653 

654 return out 

655 

656 

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

658 import warnings 

659 

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}"] 

675 

676 raise AttributeError(name)