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

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

248 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 auto_wrap_for_ansi 

17from ._compat import binary_streams 

18from ._compat import open_stream 

19from ._compat import should_strip_ansi 

20from ._compat import strip_ansi 

21from ._compat import text_streams 

22from ._compat import WIN 

23from .globals import resolve_color_default 

24 

25if t.TYPE_CHECKING: 

26 import typing_extensions as te 

27 

28 P = te.ParamSpec("P") 

29 

30R = t.TypeVar("R") 

31 

32 

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

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

35 

36 

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

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

39 

40 def wrapper(*args: P.args, **kwargs: P.kwargs) -> R | None: 

41 try: 

42 return func(*args, **kwargs) 

43 except Exception: 

44 pass 

45 return None 

46 

47 return update_wrapper(wrapper, func) 

48 

49 

50def make_str(value: t.Any) -> str: 

51 """Converts a value into a valid string.""" 

52 if isinstance(value, bytes): 

53 try: 

54 return value.decode(sys.getfilesystemencoding()) 

55 except UnicodeError: 

56 return value.decode("utf-8", "replace") 

57 return str(value) 

58 

59 

60def make_default_short_help(help: str, max_length: int = 45) -> str: 

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

62 

63 :meta private: 

64 """ 

65 # Consider only the first paragraph. 

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

67 

68 if paragraph_end != -1: 

69 help = help[:paragraph_end] 

70 

71 # Collapse newlines, tabs, and spaces. 

72 words = help.split() 

73 

74 if not words: 

75 return "" 

76 

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

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

79 words = words[1:] 

80 

81 total_length = 0 

82 last_index = len(words) - 1 

83 

84 for i, word in enumerate(words): 

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

86 

87 if total_length > max_length: # too long, truncate 

88 break 

89 

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

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

92 

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

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

95 else: 

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

97 

98 # Account for the length of the suffix. 

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

100 

101 # remove words until the length is short enough 

102 while i > 0: 

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

104 

105 if total_length <= max_length: 

106 break 

107 

108 i -= 1 

109 

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

111 

112 

113class LazyFile: 

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

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

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

117 files for writing. 

118 """ 

119 

120 name: str 

121 mode: str 

122 encoding: str | None 

123 errors: str | None 

124 atomic: bool 

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

126 should_close: bool 

127 

128 def __init__( 

129 self, 

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

131 mode: str = "r", 

132 encoding: str | None = None, 

133 errors: str | None = "strict", 

134 atomic: bool = False, 

135 ) -> None: 

136 self.name = os.fspath(filename) 

137 self.mode = mode 

138 self.encoding = encoding 

139 self.errors = errors 

140 self.atomic = atomic 

141 

142 if self.name == "-": 

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

144 else: 

145 if "r" in mode: 

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

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

148 # some cases early. 

149 open(filename, mode).close() 

150 self._f = None 

151 self.should_close = True 

152 

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

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

155 

156 def __repr__(self) -> str: 

157 if self._f is not None: 

158 return repr(self._f) 

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

160 

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

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

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

164 that Click shows. 

165 """ 

166 if self._f is not None: 

167 return self._f 

168 try: 

169 rv, self.should_close = open_stream( 

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

171 ) 

172 except OSError as e: 

173 from .exceptions import FileError 

174 

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

176 self._f = rv 

177 return rv 

178 

179 def close(self) -> None: 

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

181 if self._f is not None: 

182 self._f.close() 

183 

184 def close_intelligently(self) -> None: 

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

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

187 """ 

188 if self.should_close: 

189 self.close() 

190 

191 def __enter__(self) -> LazyFile: 

192 return self 

193 

194 def __exit__( 

195 self, 

196 exc_type: type[BaseException] | None, 

197 exc_value: BaseException | None, 

198 tb: TracebackType | None, 

199 ) -> None: 

200 self.close_intelligently() 

201 

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

203 self.open() 

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

205 

206 

207class KeepOpenFile: 

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

209 

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

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

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

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

214 

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

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

217 than the instance. 

218 """ 

219 

220 _file: t.IO[t.Any] 

221 

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

223 self._file = file 

224 

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

226 return getattr(self._file, name) 

227 

228 def __enter__(self) -> KeepOpenFile: 

229 return self 

230 

231 def __exit__( 

232 self, 

233 exc_type: type[BaseException] | None, 

234 exc_value: BaseException | None, 

235 tb: TracebackType | None, 

236 ) -> None: 

237 pass 

238 

239 def __repr__(self) -> str: 

240 return repr(self._file) 

241 

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

243 return iter(self._file) 

244 

245 

246def echo( 

247 message: object = None, 

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

249 nl: bool = True, 

250 err: bool = False, 

251 color: bool | None = None, 

252) -> None: 

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

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

255 for different data, files, and environments. 

256 

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

258 

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

260 - Supports Unicode in the Windows console. 

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

262 to text outputs. 

263 - Supports colors and styles on Windows. 

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

265 like an interactive terminal. 

266 - Always flushes the output. 

267 

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

269 converted to strings. 

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

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

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

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

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

275 an interactive terminal. 

276 

277 .. versionchanged:: 6.0 

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

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

280 will still not support Unicode. 

281 

282 .. versionchanged:: 4.0 

283 Added the ``color`` parameter. 

284 

285 .. versionadded:: 3.0 

286 Added the ``err`` parameter. 

287 

288 .. versionchanged:: 2.0 

289 Support colors on Windows if colorama is installed. 

290 """ 

291 if file is None: 

292 if err: 

293 file = _default_text_stderr() 

294 else: 

295 file = _default_text_stdout() 

296 

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

298 # pythonw on Windows. 

299 if file is None: 

300 return 

301 

302 match message: 

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

304 out = message 

305 case None: 

306 out = "" 

307 case _: 

308 out = str(message) 

309 

310 if nl: 

311 if isinstance(out, str): 

312 out += "\n" 

313 else: 

314 out += b"\n" 

315 

316 if not out: 

317 file.flush() 

318 return 

319 

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

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

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

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

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

325 binary_file = _find_binary_writer(file) 

326 if binary_file is not None: 

327 file.flush() 

328 binary_file.write(out) 

329 binary_file.flush() 

330 return 

331 

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

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

334 else: 

335 color = resolve_color_default(color) 

336 

337 if should_strip_ansi(file, color): 

338 out = strip_ansi(out) 

339 elif WIN: 

340 if auto_wrap_for_ansi is not None: 

341 file = auto_wrap_for_ansi(file, color) # type: ignore 

342 elif not 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 :param name: the name of the stream to open. Valid names are ``'stdin'``, 

353 ``'stdout'`` and ``'stderr'`` 

354 """ 

355 opener = binary_streams.get(name) 

356 if opener is None: 

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

358 return opener() 

359 

360 

361def get_text_stream( 

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

363 encoding: str | None = None, 

364 errors: str | None = "strict", 

365) -> t.TextIO: 

366 """Returns a system stream for text processing. This usually returns 

367 a wrapped stream around a binary stream returned from 

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

369 correctly configured streams. 

370 

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

372 ``'stdout'`` and ``'stderr'`` 

373 :param encoding: overrides the detected default encoding. 

374 :param errors: overrides the default error mode. 

375 """ 

376 opener = text_streams.get(name) 

377 if opener is None: 

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

379 return opener(encoding, errors) 

380 

381 

382def open_file( 

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

384 mode: str = "r", 

385 encoding: str | None = None, 

386 errors: str | None = "strict", 

387 lazy: bool = False, 

388 atomic: bool = False, 

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

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

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

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

393 

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

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

396 This makes it possible to use the function without accidentally 

397 closing a standard stream: 

398 

399 .. code-block:: python 

400 

401 with open_file(filename) as f: 

402 ... 

403 

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

405 ``stdin``/``stdout``. 

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

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

408 text mode. 

409 :param errors: The error handling mode. 

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

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

412 early, then closed until it is read again. 

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

414 on close. 

415 

416 .. versionadded:: 3.0 

417 """ 

418 if lazy: 

419 return t.cast( 

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

421 ) 

422 

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

424 

425 if not should_close: 

426 f = t.cast("t.IO[t.Any]", KeepOpenFile(f)) 

427 

428 return f 

429 

430 

431def format_filename( 

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

433 shorten: bool = False, 

434) -> str: 

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

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

437 with the replacement character ``�``. 

438 

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

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

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

442 

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

444 PEP 540, including: 

445 

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

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

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

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

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

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

452 

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

454 the filename into unicode without failing. 

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

456 path that leads up to it. 

457 """ 

458 if shorten: 

459 filename = os.path.basename(filename) 

460 else: 

461 filename = os.fspath(filename) 

462 

463 if isinstance(filename, bytes): 

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

465 else: 

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

467 "utf-8", "replace" 

468 ) 

469 

470 return filename 

471 

472 

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

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

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

476 

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

478 the following folders could be returned: 

479 

480 Mac OS X: 

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

482 Mac OS X (POSIX): 

483 ``~/.foo-bar`` 

484 Unix: 

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

486 Unix (POSIX): 

487 ``~/.foo-bar`` 

488 Windows (roaming): 

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

490 Windows (not roaming): 

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

492 

493 .. versionadded:: 2.0 

494 

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

496 and can contain whitespace. 

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

498 Has no effect otherwise. 

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

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

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

502 application support folder. 

503 """ 

504 if WIN: 

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

506 folder = os.environ.get(key) 

507 if folder is None: 

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

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

510 if force_posix: 

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

512 if sys.platform == "darwin": 

513 return os.path.join( 

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

515 ) 

516 return os.path.join( 

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

518 _posixify(app_name), 

519 ) 

520 

521 

522class PacifyFlushWrapper: 

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

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

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

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

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

528 pipe, all calls and attributes are proxied. 

529 """ 

530 

531 wrapped: t.IO[t.Any] 

532 

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

534 self.wrapped = wrapped 

535 

536 def flush(self) -> None: 

537 try: 

538 self.wrapped.flush() 

539 except OSError as e: 

540 import errno 

541 

542 if e.errno != errno.EPIPE: 

543 raise 

544 

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

546 return getattr(self.wrapped, attr) 

547 

548 

549def _detect_program_name( 

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

551) -> str: 

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

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

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

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

556 

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

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

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

560 ``sys.executable`` is not shown. 

561 

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

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

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

565 during internal testing. 

566 

567 .. versionadded:: 8.0 

568 Based on command args detection in the Werkzeug reloader. 

569 

570 :meta private: 

571 """ 

572 if _main is None: 

573 _main = sys.modules["__main__"] 

574 

575 if not path: 

576 path = sys.argv[0] 

577 

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

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

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

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

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

583 os.name == "nt" 

584 and _main.__package__ == "" 

585 and not os.path.exists(path) 

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

587 ): 

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

589 return os.path.basename(path) 

590 

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

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

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

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

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

596 

597 # A submodule like "example.cli". 

598 if name != "__main__": 

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

600 

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

602 

603 

604def _expand_args( 

605 args: cabc.Iterable[str], 

606 *, 

607 user: bool = True, 

608 env: bool = True, 

609 glob_recursive: bool = True, 

610) -> list[str]: 

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

612 

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

614 :func:`os.path.expandvars`. 

615 

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

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

618 

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

620 :param user: Expand user home directory. 

621 :param env: Expand environment variables. 

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

623 

624 .. versionchanged:: 8.1 

625 Invalid glob patterns are treated as empty expansions rather 

626 than raising an error. 

627 

628 .. versionadded:: 8.0 

629 

630 :meta private: 

631 """ 

632 from glob import glob 

633 

634 out = [] 

635 

636 for arg in args: 

637 if user: 

638 arg = os.path.expanduser(arg) 

639 

640 if env: 

641 arg = os.path.expandvars(arg) 

642 

643 try: 

644 matches = glob(arg, recursive=glob_recursive) 

645 except re.error: 

646 matches = [] 

647 

648 if not matches: 

649 out.append(arg) 

650 else: 

651 out.extend(matches) 

652 

653 return out