Initial commit: 首次建仓,建立目录结构
This commit is contained in:
188
dashboard/venv/lib/python3.12/site-packages/click/_textwrap.py
Normal file
188
dashboard/venv/lib/python3.12/site-packages/click/_textwrap.py
Normal file
@ -0,0 +1,188 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import collections.abc as cabc
|
||||
import textwrap
|
||||
from contextlib import contextmanager
|
||||
|
||||
from ._compat import _ansi_re
|
||||
from ._compat import term_len
|
||||
|
||||
|
||||
def _truncate_visible(text: str, n: int) -> str:
|
||||
"""Return the longest prefix of ``text`` containing at most ``n`` visible
|
||||
characters.
|
||||
|
||||
ANSI escape sequences inside the prefix are kept intact and do not count
|
||||
toward the visible width. A cut is never placed inside an escape sequence.
|
||||
"""
|
||||
if n <= 0:
|
||||
return ""
|
||||
|
||||
visible = 0
|
||||
i = 0
|
||||
cut = 0
|
||||
end = len(text)
|
||||
while i < end:
|
||||
m = _ansi_re.match(text, i)
|
||||
if m is not None:
|
||||
i = m.end()
|
||||
continue
|
||||
visible += 1
|
||||
i += 1
|
||||
cut = i
|
||||
if visible >= n:
|
||||
break
|
||||
return text[:cut]
|
||||
|
||||
|
||||
class TextWrapper(textwrap.TextWrapper):
|
||||
"""``textwrap.TextWrapper`` variant that measures widths by visible
|
||||
character count.
|
||||
|
||||
ANSI escape sequences embedded in chunks, indents, or the placeholder are
|
||||
excluded from the width budget. Without this, styled help text (a styled
|
||||
``Usage:`` prefix, a colorized option name, ...) would be wrapped earlier
|
||||
than its visible length warrants and tokens would split mid-word.
|
||||
"""
|
||||
|
||||
def _handle_long_word(
|
||||
self,
|
||||
reversed_chunks: list[str],
|
||||
cur_line: list[str],
|
||||
cur_len: int,
|
||||
width: int,
|
||||
) -> None:
|
||||
space_left = max(width - cur_len, 1)
|
||||
|
||||
if self.break_long_words:
|
||||
last = reversed_chunks[-1]
|
||||
cut = _truncate_visible(last, space_left)
|
||||
res = last[len(cut) :]
|
||||
cur_line.append(cut)
|
||||
reversed_chunks[-1] = res
|
||||
elif not cur_line:
|
||||
cur_line.append(reversed_chunks.pop())
|
||||
|
||||
def _wrap_chunks(self, chunks: list[str]) -> list[str]:
|
||||
"""Wrap chunks counting widths in visible characters.
|
||||
|
||||
Mirrors the algorithm of :meth:`textwrap.TextWrapper._wrap_chunks`
|
||||
with every width measurement routed through
|
||||
:func:`click._compat.term_len` instead of :func:`len`, so ANSI escape
|
||||
bytes in chunks, indents, or the placeholder do not inflate the count.
|
||||
|
||||
.. seealso::
|
||||
:class:`textwrap.TextWrapper` in the Python standard library documentation:
|
||||
https://docs.python.org/3/library/textwrap.html#textwrap.TextWrapper
|
||||
|
||||
Reference implementation in CPython:
|
||||
https://github.com/python/cpython/blob/main/Lib/textwrap.py
|
||||
"""
|
||||
lines: list[str] = []
|
||||
if self.width <= 0:
|
||||
raise ValueError(f"invalid width {self.width!r} (must be > 0)")
|
||||
if self.max_lines is not None:
|
||||
if self.max_lines > 1:
|
||||
indent = self.subsequent_indent
|
||||
else:
|
||||
indent = self.initial_indent
|
||||
if term_len(indent) + term_len(self.placeholder.lstrip()) > self.width:
|
||||
raise ValueError("placeholder too large for max width")
|
||||
|
||||
chunks.reverse()
|
||||
|
||||
while chunks:
|
||||
cur_line: list[str] = []
|
||||
cur_len = 0
|
||||
|
||||
if lines:
|
||||
indent = self.subsequent_indent
|
||||
else:
|
||||
indent = self.initial_indent
|
||||
|
||||
width = self.width - term_len(indent)
|
||||
|
||||
if self.drop_whitespace and chunks[-1].strip() == "" and lines:
|
||||
del chunks[-1]
|
||||
|
||||
while chunks:
|
||||
n = term_len(chunks[-1])
|
||||
|
||||
if cur_len + n <= width:
|
||||
cur_line.append(chunks.pop())
|
||||
cur_len += n
|
||||
|
||||
else:
|
||||
break
|
||||
|
||||
if chunks and term_len(chunks[-1]) > width:
|
||||
self._handle_long_word(chunks, cur_line, cur_len, width)
|
||||
cur_len = sum(map(term_len, cur_line))
|
||||
|
||||
if self.drop_whitespace and cur_line and cur_line[-1].strip() == "":
|
||||
cur_len -= term_len(cur_line[-1])
|
||||
del cur_line[-1]
|
||||
|
||||
if cur_line:
|
||||
if (
|
||||
self.max_lines is None
|
||||
or len(lines) + 1 < self.max_lines
|
||||
or (
|
||||
not chunks
|
||||
or self.drop_whitespace
|
||||
and len(chunks) == 1
|
||||
and not chunks[0].strip()
|
||||
)
|
||||
and cur_len <= width
|
||||
):
|
||||
lines.append(indent + "".join(cur_line))
|
||||
else:
|
||||
while cur_line:
|
||||
if (
|
||||
cur_line[-1].strip()
|
||||
and cur_len + term_len(self.placeholder) <= width
|
||||
):
|
||||
cur_line.append(self.placeholder)
|
||||
lines.append(indent + "".join(cur_line))
|
||||
break
|
||||
cur_len -= term_len(cur_line[-1])
|
||||
del cur_line[-1]
|
||||
else:
|
||||
if lines:
|
||||
prev_line = lines[-1].rstrip()
|
||||
if (
|
||||
term_len(prev_line) + term_len(self.placeholder)
|
||||
<= self.width
|
||||
):
|
||||
lines[-1] = prev_line + self.placeholder
|
||||
break
|
||||
lines.append(indent + self.placeholder.lstrip())
|
||||
break
|
||||
|
||||
return lines
|
||||
|
||||
@contextmanager
|
||||
def extra_indent(self, indent: str) -> cabc.Iterator[None]:
|
||||
old_initial_indent = self.initial_indent
|
||||
old_subsequent_indent = self.subsequent_indent
|
||||
self.initial_indent += indent
|
||||
self.subsequent_indent += indent
|
||||
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
self.initial_indent = old_initial_indent
|
||||
self.subsequent_indent = old_subsequent_indent
|
||||
|
||||
def indent_only(self, text: str) -> str:
|
||||
rv = []
|
||||
|
||||
for idx, line in enumerate(text.splitlines()):
|
||||
indent = self.initial_indent
|
||||
|
||||
if idx > 0:
|
||||
indent = self.subsequent_indent
|
||||
|
||||
rv.append(f"{indent}{line}")
|
||||
|
||||
return "\n".join(rv)
|
||||
Reference in New Issue
Block a user