summaryrefslogtreecommitdiffstats
path: root/contrib/python/wcwidth/py3
diff options
context:
space:
mode:
authorrobot-piglet <[email protected]>2026-02-21 17:34:55 +0300
committerrobot-piglet <[email protected]>2026-02-21 18:03:01 +0300
commit4a2bb48a518e079622abeee860d5d3040a401bba (patch)
tree4efcdbebaa32ad3c7e7abb57c1cbdd537d057148 /contrib/python/wcwidth/py3
parent274b52970ded8854a34077e3d0caa0164c359c76 (diff)
Intermediate changes
commit_hash:68b40ed025a133871e4c19d3a0290aa01d1c41c1
Diffstat (limited to 'contrib/python/wcwidth/py3')
-rw-r--r--contrib/python/wcwidth/py3/.dist-info/METADATA7
-rw-r--r--contrib/python/wcwidth/py3/tests/test_textwrap.py186
-rw-r--r--contrib/python/wcwidth/py3/wcwidth/__init__.py2
-rw-r--r--contrib/python/wcwidth/py3/wcwidth/textwrap.py176
-rw-r--r--contrib/python/wcwidth/py3/ya.make2
5 files changed, 279 insertions, 94 deletions
diff --git a/contrib/python/wcwidth/py3/.dist-info/METADATA b/contrib/python/wcwidth/py3/.dist-info/METADATA
index d8c25b9eee9..f6f0235df8f 100644
--- a/contrib/python/wcwidth/py3/.dist-info/METADATA
+++ b/contrib/python/wcwidth/py3/.dist-info/METADATA
@@ -1,6 +1,6 @@
Metadata-Version: 2.4
Name: wcwidth
-Version: 0.5.3
+Version: 0.6.0
Summary: Measures the displayed width of unicode strings in a terminal
Project-URL: Homepage, https://github.com/jquast/wcwidth
Author-email: Jeff Quast <[email protected]>
@@ -486,6 +486,11 @@ languages.
History
=======
+0.6.0 *2026-02-06*
+ * **New** Parameters ``expand_tabs``, ``replace_whitespace``, ``fix_sentence_endings``,
+ ``drop_whitespace``, ``max_lines``, and ``placeholder`` for `wrap()`_, completing stdlib
+ `textwrap.wrap()`_ compatibility.
+
0.5.3 *2026-01-30*
* **Bugfix** Brahmic using Virama conjunct formation. `Issue #155`_, `PR #204`_.
diff --git a/contrib/python/wcwidth/py3/tests/test_textwrap.py b/contrib/python/wcwidth/py3/tests/test_textwrap.py
index e39c27cd269..094c8e56725 100644
--- a/contrib/python/wcwidth/py3/tests/test_textwrap.py
+++ b/contrib/python/wcwidth/py3/tests/test_textwrap.py
@@ -26,7 +26,6 @@ def mock_hyperlink_ids(monkeypatch):
SGR_RED = '\x1b[31m'
-SGR_BLUE = '\x1b[34m'
SGR_BOLD = '\x1b[1m'
SGR_RESET = '\x1b[0m'
ATTRS = ('\x1b[31m', '\x1b[34m', '\x1b[4m', '\x1b[7m', '\x1b[41m', '\x1b[37m', '\x1b[107m')
@@ -59,7 +58,7 @@ def _adjust_stdlib_result(expected, kwargs):
"""
if not expected:
return expected
- if kwargs.get('drop_whitespace'):
+ if kwargs.get('drop_whitespace', True):
# Strip trailing whitespace from each line (old Python bug)
expected = [line.rstrip() for line in expected]
# Remove leading all-whitespace lines (old Python bug)
@@ -77,15 +76,17 @@ def _colorize(text):
)
-# Edge cases not covered by stdlib comparison
-BASIC_EDGE_CASES = [
+EDGE_CASES = [
('', 10, []),
(' ', 10, []),
('\u5973', 0, ['\u5973']),
+ ('\u5973', 1, ['\u5973']),
+ (ZWJ_FAMILY, 1, [ZWJ_FAMILY]),
+ (HANGUL_GA, 1, [HANGUL_GA]),
]
[email protected]('text,w,expected', BASIC_EDGE_CASES)
[email protected]('text,w,expected', EDGE_CASES)
def test_wrap_edge_cases(text, w, expected):
assert wrap(text, w) == expected
@@ -94,12 +95,6 @@ def test_wrap_initial_indent():
assert wrap('hello world', 10, initial_indent='> ') == ['> hello', 'world']
-def test_wrap_drops_trailing_whitespace():
- """Trailing whitespace stripped when drop_whitespace=True (CPython #140627)."""
- result = wrap(' Z! a bc defghij', 3)
- assert result[:3] == [' Z!', 'a', 'bc']
-
-
LONG_WORD_CASES = [
('abcdefghij', 3, True, ['abc', 'def', 'ghi', 'j']),
('abcdefghij', 3, False, ['abcdefghij']),
@@ -111,30 +106,19 @@ def test_wrap_long_words(text, w, break_long, expected):
assert wrap(text, w, break_long_words=break_long) == expected
-# Hyphen edge cases for long word breaking
HYPHEN_LONG_WORD_CASES = [
- ('a-b-c-d', 3, True, ['a-', 'b-', 'c-d']),
- ('a-b-c-d', 3, False, ['a-b', '-c-', 'd']),
- ('---', 2, True, ['--', '-']),
- ('a---b', 2, True, ['a-', '--', 'b']),
- # With propagate_sgr=True, SGR continues to next line
- ('a-\x1b[31mb', 2, True, ['a-\x1b[31m\x1b[0m', '\x1b[31mb\x1b[0m']),
+ ('a-b-c-d', 3, True, True, ['a-', 'b-', 'c-d']),
+ ('a-b-c-d', 3, False, True, ['a-b', '-c-', 'd']),
+ ('---', 2, True, True, ['--', '-']),
+ ('a---b', 2, True, True, ['a-', '--', 'b']),
+ ('a-\x1b[31mb', 2, True, True, ['a-\x1b[31m\x1b[0m', '\x1b[31mb\x1b[0m']),
+ ('a-\x1b[31mb', 2, True, False, ['a-\x1b[31m', 'b']),
]
-HYPHEN_LONG_WORD_CASES_NO_PROPAGATE = [
- # With propagate_sgr=False, SGR stays where it is
- ('a-\x1b[31mb', 2, True, ['a-\x1b[31m', 'b']),
-]
-
[email protected]('text,w,break_hyphens,expected', HYPHEN_LONG_WORD_CASES)
-def test_wrap_hyphen_long_words(text, w, break_hyphens, expected):
- assert wrap(text, w, break_on_hyphens=break_hyphens) == expected
-
-
[email protected]('text,w,break_hyphens,expected', HYPHEN_LONG_WORD_CASES_NO_PROPAGATE)
-def test_wrap_hyphen_long_words_no_propagate(text, w, break_hyphens, expected):
- assert wrap(text, w, break_on_hyphens=break_hyphens, propagate_sgr=False) == expected
[email protected]('text,w,break_hyphens,propagate,expected', HYPHEN_LONG_WORD_CASES)
+def test_wrap_hyphen_long_words(text, w, break_hyphens, propagate, expected):
+ assert wrap(text, w, break_on_hyphens=break_hyphens, propagate_sgr=propagate) == expected
# Comprehensive stdlib compatibility
@@ -147,6 +131,17 @@ TEXTWRAP_KWARGS = [
{'break_long_words': True, 'drop_whitespace': True, 'subsequent_indent': ' '},
{'break_long_words': True, 'drop_whitespace': True, 'break_on_hyphens': True},
{'break_long_words': True, 'drop_whitespace': True, 'break_on_hyphens': False},
+ {'break_long_words': True, 'drop_whitespace': False,
+ 'subsequent_indent': '', 'max_lines': 4, 'placeholder': '~'},
+ {'break_long_words': True, 'drop_whitespace': True,
+ 'max_lines': 3, 'placeholder': '...'},
+ {'break_long_words': True, 'drop_whitespace': True,
+ 'max_lines': 1, 'placeholder': '...'},
+ {'expand_tabs': False, 'break_long_words': True, 'drop_whitespace': True},
+ {'replace_whitespace': False, 'break_long_words': True,
+ 'drop_whitespace': True},
+ {'fix_sentence_endings': True, 'break_long_words': True,
+ 'drop_whitespace': True},
]
@@ -187,20 +182,6 @@ def test_wrap_multiline_matches_stdlib():
assert wrap(given, 30) == textwrap.wrap(given, 30)
-# Wide characters that exceed width=1 (tests force-grapheme logic)
-WIDE_CHAR_WIDTH_1_CASES = [
- ('\u5973', 1, ['\u5973']),
- (ZWJ_FAMILY, 1, [ZWJ_FAMILY]),
- (HANGUL_GA, 1, [HANGUL_GA]),
-]
-
-
[email protected]('text,w,expected', WIDE_CHAR_WIDTH_1_CASES)
-def test_wrap_wide_char_width_1(text, w, expected):
- assert wrap(text, w) == expected
-
-
-# Unicode width-aware wrapping
UNICODE_CASES = [
# CJK (2 cells each)
('\u4e2d\u6587\u5b57\u7b26', 4, ['\u4e2d\u6587', '\u5b57\u7b26']),
@@ -244,24 +225,15 @@ SEQUENCE_CASES = [
('abc\x1bdefghij', 3, ['abc\x1b', 'def', 'ghi', 'j']),
]
-# Old behavior tests (propagate_sgr=False)
SEQUENCE_CASES_NO_PROPAGATE = [
- (f'{SGR_RED}red{SGR_RESET} blue', 4, [f'{SGR_RED}red{SGR_RESET}', 'blue']),
(f'hello{SGR_RED} world', 6, [f'hello{SGR_RED}', 'world']),
- (f'{SGR_RED}{SGR_RESET}', 10, [f'{SGR_RED}{SGR_RESET}']),
- (f'hello {SGR_RED}{SGR_RESET}world', 6, ['hello', f'{SGR_RED}{SGR_RESET}world']),
- # Sequences preserved where they are, not propagated
('x\x1b[31mabcdefghij\x1b[0m', 3, ['x\x1b[31mab', 'cde', 'fgh', 'ij\x1b[0m']),
]
@pytest.mark.parametrize('text,w,expected', SEQUENCE_CASES)
def test_wrap_sequences(benchmark, text, w, expected):
- result = benchmark(wrap, text, w)
- if any('\x1b' in e or '\x00' <= e[0] < '\x20' for e in expected if e):
- assert result == expected
- else:
- assert result == expected
+ assert benchmark(wrap, text, w) == expected
@pytest.mark.parametrize('text,w,expected', SEQUENCE_CASES_NO_PROPAGATE)
@@ -445,3 +417,107 @@ def test_wrap_hyperlink_word_boundary(text, w, expected):
"""OSC hyperlink sequences should act as word boundaries."""
result = wrap(text, w)
assert result == expected
+
+
+PLACEHOLDER_STDLIB_CASES = [
+ ('The quick brown fox jumps over the lazy dog',
+ {'width': 10, 'max_lines': 3, 'placeholder': '...'}),
+ ('1234567890 1234567890 extra',
+ {'width': 10, 'max_lines': 2, 'placeholder': '...'}),
+ ('1234567890 1234567890',
+ {'width': 10, 'max_lines': 1, 'placeholder': '...'}),
+ ('short 1234567890 extra',
+ {'width': 10, 'max_lines': 2, 'placeholder': '...'}),
+ ('hello world',
+ {'width': 10, 'max_lines': 5, 'placeholder': '...'}),
+ ('hello world foo bar baz',
+ {'width': 8, 'max_lines': 2, 'placeholder': ' [...]'}),
+ ('a bb ccc',
+ {'width': 3, 'max_lines': 2, 'placeholder': '.'}),
+ ('a bb ccc dddd',
+ {'width': 4, 'max_lines': 3, 'placeholder': '~'}),
+ (' a ', {'width': 1, 'max_lines': 2, 'drop_whitespace': False, 'placeholder': '~'}),
+ ('hello world', {'width': 20, 'max_lines': 5}),
+ ('ab cd', {'width': 5, 'max_lines': 1}),
+ ('aaa bbb ccc', {'width': 3, 'max_lines': 2, 'placeholder': '...'}),
+ ('hello world foo bar',
+ {'width': 10, 'subsequent_indent': ' ', 'max_lines': 2, 'placeholder': '...'}),
+ ('hello world foo bar',
+ {'width': 10, 'initial_indent': '> ', 'max_lines': 2, 'placeholder': '...'}),
+]
+
+
[email protected]('text,kwargs', PLACEHOLDER_STDLIB_CASES)
+def test_wrap_max_lines_matches_stdlib(text, kwargs):
+ expected = _adjust_stdlib_result(textwrap.wrap(text, **kwargs), kwargs)
+ assert wrap(text, **kwargs) == expected
+
+
+def test_wrap_placeholder_too_large():
+ with pytest.raises(ValueError, match="placeholder too large"):
+ wrap('hello', width=3, max_lines=1, placeholder='.....')
+ with pytest.raises(ValueError):
+ textwrap.wrap('fox', width=1, max_lines=3, placeholder='...')
+
+
+MAX_LINES_SEQUENCE_CASES = [
+ (f'{SGR_RED}hello world foo bar{SGR_RESET}',
+ 8, 2, '...', [f'{SGR_RED}hello{SGR_RESET}', f'{SGR_RED}world...{SGR_RESET}']),
+ (f'{SGR_RED}hello{SGR_RESET} world foo',
+ 8, 2, '...', [f'{SGR_RED}hello{SGR_RESET}', 'world...']),
+ (f'{SGR_RED}hello{SGR_RESET} world',
+ 6, 1, '.', [f'{SGR_RED}hello{SGR_RESET}.']),
+ ('\u4e2d\u6587 \u5b57\u7b26 hello', 5, 1, '~', ['\u4e2d\u6587~']),
+ ('\u4e2d\u6587 \u5b57\u7b26 hello world', 5, 2, '~', ['\u4e2d\u6587', '\u5b57\u7b26~']),
+ ('\u4e2d\u6587\u5b57\u7b26 hello', 12, 1, '...', ['\u4e2d\u6587\u5b57\u7b26...']),
+]
+
+
[email protected]('text,w,ml,ph,expected', MAX_LINES_SEQUENCE_CASES)
+def test_wrap_max_lines_sequences(text, w, ml, ph, expected):
+ assert wrap(text, w, max_lines=ml, placeholder=ph) == expected
+
+
+def test_wrap_max_lines_hyperlink_closed():
+ """Truncation inside a hyperlink closes it before the placeholder."""
+ text = f'{OSC_START_ST}Click here please{OSC_END_ST}'
+ result = wrap(text, 10, max_lines=1, placeholder='...')
+ assert len(result) == 1
+ assert result[0].endswith(f'{OSC_END_ST}...')
+ assert OSC_START_ST in result[0]
+
+
+def test_wrap_max_lines_hyperlink_close_on_prev_line():
+ """Fallback to previous line preserves hyperlink close sequence."""
+ text = f'{OSC_START_ST}ab{OSC_END_ST} cccccccccc ddddd'
+ result = wrap(text, 10, max_lines=2, placeholder='...')
+ assert result == [f'{OSC_START_ST}ab{OSC_END_ST}...']
+
+
+# -- expand_tabs, replace_whitespace, fix_sentence_endings --
+
+STDLIB_PARAM_CASES = [
+ ('hello\tworld', {'width': 20, 'expand_tabs': False, 'replace_whitespace': False}),
+ ('hello\tworld foo\tbar baz', {'width': 12, 'expand_tabs': False, 'tabsize': 8}),
+ ('hello\nworld', {'width': 20, 'replace_whitespace': False}),
+ ('a\t b\n c', {'width': 20, 'replace_whitespace': False}),
+ ('Hello world. This is a test. More text.', {'width': 20, 'fix_sentence_endings': True}),
+ ('Dr. Smith went to Washington. He left.', {'width': 20, 'fix_sentence_endings': True}),
+]
+
+
[email protected]('text,kwargs', STDLIB_PARAM_CASES)
+def test_wrap_stdlib_params(text, kwargs):
+ assert wrap(text, **kwargs) == textwrap.wrap(text, **kwargs)
+
+
+def test_wrap_expand_tabs_false_with_sequences():
+ text = f'{SGR_RED}a\tb{SGR_RESET}'
+ result = wrap(text, 20, expand_tabs=False, replace_whitespace=False)
+ assert '\t' in _strip(result[0])
+
+
+def test_wrap_replace_whitespace_false_newlines_zero_width():
+ """Newlines have zero display width, so more text fits per line than stdlib."""
+ assert wrap('hello\nworld foo\nbar', 10, replace_whitespace=False) == [
+ 'hello\nworld', 'foo\nbar']
diff --git a/contrib/python/wcwidth/py3/wcwidth/__init__.py b/contrib/python/wcwidth/py3/wcwidth/__init__.py
index ffb13207b61..400c8a61935 100644
--- a/contrib/python/wcwidth/py3/wcwidth/__init__.py
+++ b/contrib/python/wcwidth/py3/wcwidth/__init__.py
@@ -40,4 +40,4 @@ __all__ = ('wcwidth', 'wcswidth', 'width', 'iter_sequences', 'iter_graphemes',
# Using 'hatchling', it does not seem to provide the pyproject.toml nicety, "dynamic = ['version']"
# like flit_core, maybe there is some better way but for now we have to duplicate it in both places
-__version__ = '0.5.3'
+__version__ = '0.6.0'
diff --git a/contrib/python/wcwidth/py3/wcwidth/textwrap.py b/contrib/python/wcwidth/py3/wcwidth/textwrap.py
index e6cca9161e8..4582cd5e089 100644
--- a/contrib/python/wcwidth/py3/wcwidth/textwrap.py
+++ b/contrib/python/wcwidth/py3/wcwidth/textwrap.py
@@ -228,6 +228,16 @@ class SequenceTextWrapper(textwrap.TextWrapper):
if not chunks:
return []
+ if self.max_lines is not None:
+ if self.max_lines > 1:
+ indent = self.subsequent_indent
+ else:
+ indent = self.initial_indent
+ if (self._width(indent)
+ + self._width(self.placeholder.lstrip())
+ > self.width):
+ raise ValueError("placeholder too large for max width")
+
lines: list[str] = []
is_first_line = True
@@ -296,48 +306,90 @@ class SequenceTextWrapper(textwrap.TextWrapper):
current_line[-1] = current_line[-1] + sequences
if current_line:
- line_content = ''.join(current_line)
+ # Check whether this is a normal append or max_lines
+ # truncation. Matches stdlib textwrap precedence:
+ # normal if max_lines not set, not yet reached, or no
+ # remaining visible content that would need truncation.
+ no_more_content = (
+ not chunks or
+ self.drop_whitespace and
+ len(chunks) == 1 and
+ not self._strip_sequences(chunks[0]).strip()
+ )
+ if (self.max_lines is None or
+ len(lines) + 1 < self.max_lines or
+ no_more_content
+ and current_width <= line_width):
+ line_content = ''.join(current_line)
- # Track hyperlink state through this line's content
- new_state = self._track_hyperlink_state(line_content, hyperlink_state)
+ # Track hyperlink state through this line's content
+ new_state = self._track_hyperlink_state(line_content, hyperlink_state)
- # If we end inside a hyperlink, append close sequence
- if new_state is not None:
- # Ensure we have an id for continuation
- if current_hyperlink_id is None:
- if 'id=' in new_state.params:
- current_hyperlink_id = new_state.params
- elif new_state.params:
- # Prepend id to existing params (per OSC 8 spec, params can have
- # multiple key=value pairs separated by :)
- current_hyperlink_id = (
- f'id={self._next_hyperlink_id()}:{new_state.params}')
- else:
- current_hyperlink_id = f'id={self._next_hyperlink_id()}'
- line_content = line_content + _make_hyperlink_close(new_state.terminator)
+ # If we end inside a hyperlink, append close sequence
+ if new_state is not None:
+ # Ensure we have an id for continuation
+ if current_hyperlink_id is None:
+ if 'id=' in new_state.params:
+ current_hyperlink_id = new_state.params
+ elif new_state.params:
+ # Prepend id to existing params (per OSC 8 spec, params can have
+ # multiple key=value pairs separated by :)
+ current_hyperlink_id = (
+ f'id={self._next_hyperlink_id()}:{new_state.params}')
+ else:
+ current_hyperlink_id = f'id={self._next_hyperlink_id()}'
+ line_content += _make_hyperlink_close(new_state.terminator)
- # Also need to inject the id into the opening sequence if it didn't have one
- if 'id=' not in new_state.params:
- # Find and replace the original open sequence with one that has id
- old_open = _make_hyperlink_open(
- new_state.url, new_state.params, new_state.terminator)
- new_open = _make_hyperlink_open(
+ # Also need to inject the id into the opening
+ # sequence if it didn't have one
+ if 'id=' not in new_state.params:
+ # Find and replace the original open sequence with one that has id
+ old_open = _make_hyperlink_open(
+ new_state.url, new_state.params, new_state.terminator)
+ new_open = _make_hyperlink_open(
+ new_state.url, current_hyperlink_id, new_state.terminator)
+ line_content = line_content.replace(old_open, new_open, 1)
+
+ # Update state for next line, using computed id
+ hyperlink_state = _HyperlinkState(
new_state.url, current_hyperlink_id, new_state.terminator)
- line_content = line_content.replace(old_open, new_open, 1)
+ else:
+ hyperlink_state = None
+ current_hyperlink_id = None # Reset id when hyperlink closes
- # Update state for next line, using computed id
- hyperlink_state = _HyperlinkState(
- new_state.url, current_hyperlink_id, new_state.terminator)
+ # Strip trailing whitespace when drop_whitespace is enabled
+ # (matches CPython #140627 fix behavior)
+ if self.drop_whitespace:
+ line_content = line_content.rstrip()
+ lines.append(indent + line_content)
+ is_first_line = False
else:
- hyperlink_state = None
- current_hyperlink_id = None # Reset id when hyperlink closes
-
- # Strip trailing whitespace when drop_whitespace is enabled
- # (matches CPython #140627 fix behavior)
- if self.drop_whitespace:
- line_content = line_content.rstrip()
- lines.append(indent + line_content)
- is_first_line = False
+ # max_lines reached with remaining content —
+ # pop chunks until placeholder fits, then break.
+ placeholder_w = self._width(self.placeholder)
+ while current_line:
+ last_text = self._strip_sequences(current_line[-1])
+ if (last_text.strip()
+ and current_width + placeholder_w <= line_width):
+ line_content = ''.join(current_line)
+ new_state = self._track_hyperlink_state(
+ line_content, hyperlink_state)
+ if new_state is not None:
+ line_content += _make_hyperlink_close(
+ new_state.terminator)
+ lines.append(indent + line_content + self.placeholder)
+ break
+ current_width -= self._width(current_line[-1])
+ del current_line[-1]
+ else:
+ if lines:
+ prev_line = self._rstrip_visible(lines[-1])
+ if (self._width(prev_line) + placeholder_w
+ <= self.width):
+ lines[-1] = prev_line + self.placeholder
+ break
+ lines.append(indent + self.placeholder.lstrip())
+ break
return lines
@@ -461,15 +513,40 @@ class SequenceTextWrapper(textwrap.TextWrapper):
"""Find the end position of the first grapheme."""
return len(next(iter_graphemes(text)))
+ def _rstrip_visible(self, text: str) -> str:
+ """Strip trailing visible whitespace, preserving trailing sequences."""
+ segments = list(iter_sequences(text))
+ last_vis = -1
+ for i, (segment, is_seq) in enumerate(segments):
+ if not is_seq and segment.rstrip():
+ last_vis = i
+ if last_vis == -1:
+ return ''
+ result = []
+ for i, (segment, is_seq) in enumerate(segments):
+ if i < last_vis:
+ result.append(segment)
+ elif i == last_vis:
+ result.append(segment.rstrip())
+ elif is_seq:
+ result.append(segment)
+ return ''.join(result)
+
def wrap(text: str, width: int = 70, *,
control_codes: Literal['parse', 'strict', 'ignore'] = 'parse',
tabsize: int = 8,
+ expand_tabs: bool = True,
+ replace_whitespace: bool = True,
ambiguous_width: int = 1,
initial_indent: str = '',
subsequent_indent: str = '',
+ fix_sentence_endings: bool = False,
break_long_words: bool = True,
break_on_hyphens: bool = True,
+ drop_whitespace: bool = True,
+ max_lines: int | None = None,
+ placeholder: str = ' [...]',
propagate_sgr: bool = True) -> list[str]:
r"""
Wrap text to fit within given width, returning a list of wrapped lines.
@@ -482,12 +559,28 @@ def wrap(text: str, width: int = 70, *,
:param width: Maximum line width in display cells.
:param control_codes: How to handle terminal sequences (see :func:`~.width`).
:param tabsize: Tab stop width for tab expansion.
+ :param expand_tabs: If True (default), tab characters are expanded
+ to spaces using ``tabsize``.
+ :param replace_whitespace: If True (default), each whitespace character
+ is replaced with a single space after tab expansion. When False,
+ control whitespace like ``\n`` has zero display width (unlike
+ :func:`textwrap.wrap` which counts ``len()``), so wrap points
+ may differ from stdlib for non-space whitespace characters.
:param ambiguous_width: Width to use for East Asian Ambiguous (A)
characters. Default is ``1`` (narrow). Set to ``2`` for CJK contexts.
:param initial_indent: String prepended to first line.
:param subsequent_indent: String prepended to subsequent lines.
+ :param fix_sentence_endings: If True, ensure sentences are always
+ separated by exactly two spaces.
:param break_long_words: If True, break words longer than width.
:param break_on_hyphens: If True, allow breaking at hyphens.
+ :param drop_whitespace: If True (default), whitespace at the beginning
+ and end of each line (after wrapping but before indenting) is dropped.
+ Set to False to preserve whitespace.
+ :param max_lines: If set, output contains at most this many lines, with
+ ``placeholder`` appended to the last line if the text was truncated.
+ :param placeholder: String appended to the last line when text is
+ truncated by ``max_lines``. Default is ``' [...]'``.
:param propagate_sgr: If True (default), SGR (terminal styling) sequences
are propagated across wrapped lines. Each line ends with a reset
sequence and the next line begins with the active style restored.
@@ -526,6 +619,10 @@ def wrap(text: str, width: int = 70, *,
.. versionchanged:: 0.5.0
Added ``propagate_sgr`` parameter (default True).
+ .. versionchanged:: 0.6.0
+ Added ``expand_tabs``, ``replace_whitespace``, ``fix_sentence_endings``,
+ ``drop_whitespace``, ``max_lines``, and ``placeholder`` parameters.
+
Example::
>>> from wcwidth import wrap
@@ -534,15 +631,22 @@ def wrap(text: str, width: int = 70, *,
>>> wrap('中文字符', 4) # CJK characters (2 cells each)
['中文', '字符']
"""
+ # pylint: disable=too-many-arguments,too-many-locals
wrapper = SequenceTextWrapper(
width=width,
control_codes=control_codes,
tabsize=tabsize,
+ expand_tabs=expand_tabs,
+ replace_whitespace=replace_whitespace,
ambiguous_width=ambiguous_width,
initial_indent=initial_indent,
subsequent_indent=subsequent_indent,
+ fix_sentence_endings=fix_sentence_endings,
break_long_words=break_long_words,
break_on_hyphens=break_on_hyphens,
+ drop_whitespace=drop_whitespace,
+ max_lines=max_lines,
+ placeholder=placeholder,
)
lines = wrapper.wrap(text)
diff --git a/contrib/python/wcwidth/py3/ya.make b/contrib/python/wcwidth/py3/ya.make
index 8ae72ce50be..c50d8fe4296 100644
--- a/contrib/python/wcwidth/py3/ya.make
+++ b/contrib/python/wcwidth/py3/ya.make
@@ -2,7 +2,7 @@
PY3_LIBRARY()
-VERSION(0.5.3)
+VERSION(0.6.0)
LICENSE(MIT)