Skip to content

Don't break usage-line options at a hyphen - #3810

Open
vikasvardhanv wants to merge 1 commit into
pallets:mainfrom
vikasvardhanv:fix-3362-usage-hyphen-break
Open

Don't break usage-line options at a hyphen#3810
vikasvardhanv wants to merge 1 commit into
pallets:mainfrom
vikasvardhanv:fix-3362-usage-hyphen-break

Conversation

@vikasvardhanv

Copy link
Copy Markdown

Fixes #3362

The bug

When HelpFormatter.write_usage wraps the usage line, an option name or metavar containing hyphens can be split at one of those hyphens if it lands at the wrap boundary. For example:

import click

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",
]
f = click.HelpFormatter(width=65)
f.write_usage("program", " ".join(options))
print(f.getvalue())

produces broken option names:

Usage: program --enable-verbose-logging --output-file-path --max-
               retry-count --disable-cache-mode --config-file-
               location ...

Root cause

write_usage wraps the arguments through wrap_text, which builds a TextWrapper. textwrap.TextWrapper defaults to break_on_hyphens=True, so it treats each hyphen inside a token as an allowed break point and can wrap mid-option. There was no way for the caller to influence this.

The fix

  • Add a break_on_hyphens parameter to wrap_text, defaulting to True so existing behavior (help/prose wrapping) is unchanged.
  • Pass break_on_hyphens=False from write_usage, so usage tokens only ever wrap at the spaces between them.

The wide-character/ANSI-aware wrapping and every other code path are untouched. Output now matches the expected result from the issue:

Usage: program --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

Testing

  • Added test_write_usage_does_not_break_options_at_hyphen in tests/test_formatting.py. It fails on main (options split at hyphens) and passes with this change.
  • Full suite: 1991 passed, 25 skipped, 1 xfailed.
  • ruff check, ruff format --check, and mypy src/click/formatting.py are all clean.
  • Added a CHANGES.md entry and .. versionchanged:: note in the wrap_text docstring.

`HelpFormatter.write_usage` wrapped the arguments with the standard
library default of `break_on_hyphens=True`, so a long option name or
metavar such as `--max-retry-count` could be split at one of its hyphens
when it landed at the wrap boundary, producing output like `--max-\n
retry-count`.

Add a `break_on_hyphens` parameter to `wrap_text` (defaulting to `True`
to preserve existing behavior) and pass `break_on_hyphens=False` from
`write_usage`, so usage tokens only ever wrap at the spaces between them.

Closes pallets#3362

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

HelpFormatter.write_usage breaks options at a hyphen

1 participant