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

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

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

45 

46 return update_wrapper(wrapper, func) 

47 

48 

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) 

57 

58 

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

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

61 

62 :meta private: 

63 """ 

64 # Consider only the first paragraph. 

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

66 

67 if paragraph_end != -1: 

68 help = help[:paragraph_end] 

69 

70 # Collapse newlines, tabs, and spaces. 

71 words = help.split() 

72 

73 if not words: 

74 return "" 

75 

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

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

78 words = words[1:] 

79 

80 total_length = 0 

81 last_index = len(words) - 1 

82 

83 for i, word in enumerate(words): 

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

85 

86 if total_length > max_length: # too long, truncate 

87 break 

88 

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

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

91 

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 

96 

97 # Account for the length of the suffix. 

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

99 

100 # remove words until the length is short enough 

101 while i > 0: 

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

103 

104 if total_length <= max_length: 

105 break 

106 

107 i -= 1 

108 

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

110 

111 

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

118 

119 name: str 

120 mode: str 

121 encoding: str | None 

122 errors: str | None 

123 atomic: bool 

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

125 should_close: bool 

126 

127 def __init__( 

128 self, 

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

130 mode: str = "r", 

131 encoding: str | None = None, 

132 errors: str | None = "strict", 

133 atomic: bool = False, 

134 ) -> None: 

135 self.name = os.fspath(filename) 

136 self.mode = mode 

137 self.encoding = encoding 

138 self.errors = errors 

139 self.atomic = atomic 

140 

141 if self.name == "-": 

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

143 else: 

144 if "r" in mode: 

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

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

147 # some cases early. 

148 open(filename, mode).close() 

149 self._f = None 

150 self.should_close = True 

151 

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

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

154 

155 def __repr__(self) -> str: 

156 if self._f is not None: 

157 return repr(self._f) 

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

159 

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

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

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

163 that Click shows. 

164 """ 

165 if self._f is not None: 

166 return self._f 

167 try: 

168 rv, self.should_close = open_stream( 

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

170 ) 

171 except OSError as e: 

172 from .exceptions import FileError 

173 

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

175 self._f = rv 

176 return rv 

177 

178 def close(self) -> None: 

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

180 if self._f is not None: 

181 self._f.close() 

182 

183 def close_intelligently(self) -> None: 

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

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

186 """ 

187 if self.should_close: 

188 self.close() 

189 

190 def __enter__(self) -> LazyFile: 

191 return self 

192 

193 def __exit__( 

194 self, 

195 exc_type: type[BaseException] | None, 

196 exc_value: BaseException | None, 

197 tb: TracebackType | None, 

198 ) -> None: 

199 self.close_intelligently() 

200 

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

202 self.open() 

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

204 

205 

206class KeepOpenFile: 

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

208 

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

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

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

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

213 

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

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

216 than the instance. 

217 """ 

218 

219 _file: t.IO[t.Any] 

220 

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

222 self._file = file 

223 

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

225 return getattr(self._file, name) 

226 

227 def __enter__(self) -> KeepOpenFile: 

228 return self 

229 

230 def __exit__( 

231 self, 

232 exc_type: type[BaseException] | None, 

233 exc_value: BaseException | None, 

234 tb: TracebackType | None, 

235 ) -> None: 

236 pass 

237 

238 def __repr__(self) -> str: 

239 return repr(self._file) 

240 

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

242 return iter(self._file) 

243 

244 

245def echo( 

246 message: object = None, 

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

248 nl: bool = True, 

249 err: bool = False, 

250 color: bool | None = None, 

251) -> None: 

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

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

254 for different data, files, and environments. 

255 

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

257 

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

259 - Supports Unicode in the Windows console. 

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

261 to text outputs. 

262 - Supports colors and styles on Windows. 

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

264 like an interactive terminal. 

265 - Always flushes the output. 

266 

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

268 converted to strings. 

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

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

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

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

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

274 an interactive terminal. 

275 

276 .. versionchanged:: 8.5 

277 Colorama is no longer used for color on Windows. 

278 

279 .. versionchanged:: 6.0 

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

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

282 will still not support Unicode. 

283 

284 .. versionchanged:: 4.0 

285 Added the ``color`` parameter. 

286 

287 .. versionadded:: 3.0 

288 Added the ``err`` parameter. 

289 

290 .. versionchanged:: 2.0 

291 Support colors on Windows if colorama is installed. 

292 """ 

293 if file is None: 

294 if err: 

295 file = _default_text_stderr() 

296 else: 

297 file = _default_text_stdout() 

298 

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

300 # pythonw on Windows. 

301 if file is None: 

302 return 

303 

304 match message: 

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

306 out = message 

307 case None: 

308 out = "" 

309 case _: 

310 out = str(message) 

311 

312 if nl: 

313 if isinstance(out, str): 

314 out += "\n" 

315 else: 

316 out += b"\n" 

317 

318 if not out: 

319 file.flush() 

320 return 

321 

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

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

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

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

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

327 binary_file = _find_binary_writer(file) 

328 if binary_file is not None: 

329 file.flush() 

330 binary_file.write(out) 

331 binary_file.flush() 

332 return 

333 

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

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

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

337 out = strip_ansi(out) 

338 

339 file.write(out) # type: ignore 

340 file.flush() 

341 

342 

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

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

345 

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

347 ``'stdout'`` and ``'stderr'`` 

348 """ 

349 opener = binary_streams.get(name) 

350 if opener is None: 

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

352 return opener() 

353 

354 

355def get_text_stream( 

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

357 encoding: str | None = None, 

358 errors: str | None = "strict", 

359) -> t.TextIO: 

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

361 a wrapped stream around a binary stream returned from 

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

363 correctly configured streams. 

364 

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

366 ``'stdout'`` and ``'stderr'`` 

367 :param encoding: overrides the detected default encoding. 

368 :param errors: overrides the default error mode. 

369 """ 

370 opener = text_streams.get(name) 

371 if opener is None: 

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

373 return opener(encoding, errors) 

374 

375 

376def open_file( 

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

378 mode: str = "r", 

379 encoding: str | None = None, 

380 errors: str | None = "strict", 

381 lazy: bool = False, 

382 atomic: bool = False, 

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

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

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

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

387 

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

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

390 This makes it possible to use the function without accidentally 

391 closing a standard stream: 

392 

393 .. code-block:: python 

394 

395 with open_file(filename) as f: 

396 ... 

397 

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

399 ``stdin``/``stdout``. 

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

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

402 text mode. 

403 :param errors: The error handling mode. 

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

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

406 early, then closed until it is read again. 

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

408 on close. 

409 

410 .. versionadded:: 3.0 

411 """ 

412 if lazy: 

413 return t.cast( 

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

415 ) 

416 

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

418 

419 if not should_close: 

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

421 

422 return f 

423 

424 

425def format_filename( 

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

427 shorten: bool = False, 

428) -> str: 

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

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

431 with the replacement character ``�``. 

432 

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

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

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

436 

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

438 PEP 540, including: 

439 

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

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

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

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

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

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

446 

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

448 the filename into unicode without failing. 

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

450 path that leads up to it. 

451 """ 

452 if shorten: 

453 filename = os.path.basename(filename) 

454 else: 

455 filename = os.fspath(filename) 

456 

457 if isinstance(filename, bytes): 

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

459 else: 

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

461 "utf-8", "replace" 

462 ) 

463 

464 return filename 

465 

466 

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

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

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

470 

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

472 the following folders could be returned: 

473 

474 Mac OS X: 

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

476 Mac OS X (POSIX): 

477 ``~/.foo-bar`` 

478 Unix: 

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

480 Unix (POSIX): 

481 ``~/.foo-bar`` 

482 Windows (roaming): 

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

484 Windows (not roaming): 

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

486 

487 .. versionadded:: 2.0 

488 

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

490 and can contain whitespace. 

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

492 Has no effect otherwise. 

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

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

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

496 application support folder. 

497 """ 

498 if WIN: 

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

500 folder = os.environ.get(key) 

501 if folder is None: 

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

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

504 if force_posix: 

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

506 if sys.platform == "darwin": 

507 return os.path.join( 

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

509 ) 

510 return os.path.join( 

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

512 _posixify(app_name), 

513 ) 

514 

515 

516class PacifyFlushWrapper: 

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

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

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

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

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

522 pipe, all calls and attributes are proxied. 

523 """ 

524 

525 wrapped: t.IO[t.Any] 

526 

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

528 self.wrapped = wrapped 

529 

530 def flush(self) -> None: 

531 try: 

532 self.wrapped.flush() 

533 except OSError as e: 

534 import errno 

535 

536 if e.errno != errno.EPIPE: 

537 raise 

538 

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

540 return getattr(self.wrapped, attr) 

541 

542 

543def _detect_program_name( 

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

545) -> str: 

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

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

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

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

550 

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

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

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

554 ``sys.executable`` is not shown. 

555 

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

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

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

559 during internal testing. 

560 

561 .. versionadded:: 8.0 

562 Based on command args detection in the Werkzeug reloader. 

563 

564 :meta private: 

565 """ 

566 if _main is None: 

567 _main = sys.modules["__main__"] 

568 

569 if not path: 

570 path = sys.argv[0] 

571 

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

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

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

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

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

577 os.name == "nt" 

578 and _main.__package__ == "" 

579 and not os.path.exists(path) 

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

581 ): 

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

583 return os.path.basename(path) 

584 

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

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

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

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

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

590 

591 # A submodule like "example.cli". 

592 if name != "__main__": 

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

594 

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

596 

597 

598def _expand_args( 

599 args: cabc.Iterable[str], 

600 *, 

601 user: bool = True, 

602 env: bool = True, 

603 glob_recursive: bool = True, 

604) -> list[str]: 

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

606 

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

608 :func:`os.path.expandvars`. 

609 

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

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

612 

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

614 :param user: Expand user home directory. 

615 :param env: Expand environment variables. 

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

617 

618 .. versionchanged:: 8.1 

619 Invalid glob patterns are treated as empty expansions rather 

620 than raising an error. 

621 

622 .. versionadded:: 8.0 

623 

624 :meta private: 

625 """ 

626 from glob import glob 

627 

628 out = [] 

629 

630 for arg in args: 

631 if user: 

632 arg = os.path.expanduser(arg) 

633 

634 if env: 

635 arg = os.path.expandvars(arg) 

636 

637 try: 

638 matches = glob(arg, recursive=glob_recursive) 

639 except re.error: 

640 matches = [] 

641 

642 if not matches: 

643 out.append(arg) 

644 else: 

645 out.extend(matches) 

646 

647 return out