Skip to content
Closed

AI junk #3797

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
## Version 8.6.0

Unreleased

- The usage line no longer breaks hyphenated tokens such as
`--option-name` at a hyphen when they reach the wrap boundary.
`wrap_text()` gained a `break_on_hyphens` parameter to control this.
{issue}`3362`


## Version 8.5.0

Released 2026-08-24
Expand Down
15 changes: 14 additions & 1 deletion src/click/formatting.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ def wrap_text(
initial_indent: str = "",
subsequent_indent: str = "",
preserve_paragraphs: bool = False,
break_on_hyphens: bool = True,
) -> str:
"""A helper function that intelligently wraps text. By default, it
assumes that it operates on a single paragraph of text but if the
Expand All @@ -52,12 +53,18 @@ def wrap_text(
each consecutive line.
:param preserve_paragraphs: if this flag is set then the wrapping will
intelligently handle paragraphs.
:param break_on_hyphens: whether wrapping may break lines at hyphens
within a word. Disable this to keep hyphenated
tokens such as ``--option-name`` intact.

.. versionchanged:: 8.4.0
Width is measured in visible characters. ANSI escape sequences in
``text``, ``initial_indent``, or ``subsequent_indent`` no longer
count toward the width budget, so styled input wraps based on what
the user sees instead of raw byte length.

.. versionchanged:: 8.6.0
Added the ``break_on_hyphens`` parameter.
"""
from ._textwrap import TextWrapper

Expand All @@ -67,6 +74,7 @@ def wrap_text(
initial_indent=initial_indent,
subsequent_indent=subsequent_indent,
replace_whitespace=False,
break_on_hyphens=break_on_hyphens,
)
if not preserve_paragraphs:
return wrapper.fill(text)
Expand Down Expand Up @@ -186,6 +194,7 @@ def write_usage(self, prog: str, args: str = "", prefix: str | None = None) -> N
text_width,
initial_indent=usage_prefix,
subsequent_indent=indent,
break_on_hyphens=False,
)
)
else:
Expand All @@ -195,7 +204,11 @@ def write_usage(self, prog: str, args: str = "", prefix: str | None = None) -> N
indent = " " * (max(self.current_indent, term_len(prefix)) + 4)
self.write(
wrap_text(
args, text_width, initial_indent=indent, subsequent_indent=indent
args,
text_width,
initial_indent=indent,
subsequent_indent=indent,
break_on_hyphens=False,
)
)

Expand Down
31 changes: 31 additions & 0 deletions tests/test_formatting.py
Original file line number Diff line number Diff line change
Expand Up @@ -517,6 +517,37 @@ def test_write_usage_styled_prefix_keeps_options_on_one_line():
assert visible == "Usage: cli [OPTIONS]\n"


def test_write_usage_does_not_break_options_at_hyphens():
"""Issue #3362: hyphenated tokens (e.g. ``--option-name``) on the usage
line must not be broken at a hyphen when they hit the wrap boundary.
"""
options = [
"--enable-verbose-logging",
"--output-file-path",
"--max-retry-count",
"--disable-cache-mode",
"--config-file-location",
"--user-auth-token",
"--auto-update-interval",
"--force-overwrite-existing",
"--network-timeout-seconds",
"--debug-trace-enabled",
]

formatter = click.HelpFormatter(width=65)
formatter.write_usage("program", " ".join(options))
rendered = formatter.getvalue()

# No wrapped line may end on a mid-token hyphen.
for line in rendered.splitlines():
assert not line.rstrip().endswith("-")

# Every option token survives intact across the wrap.
tokens = rendered.replace("Usage:", "").split()
assert tokens[0] == "program"
assert tokens[1:] == options


@pytest.mark.parametrize(
("formatter_kwargs", "current_indent", "prog", "args", "prefix", "expected"),
[
Expand Down