Repository: pallets/click (python)

Issue:
Automatic short help should also take linebreak into account
PEP257 defines a summary line for [multi-line docstrings](https://www.python.org/dev/peps/pep-0257/#multi-line-docstrings) just like a [one-line docstring](https://www.python.org/dev/peps/pep-0257/#one-line-docstrings) to be a phrase ending in a period.

Click automatically uses everything up to the first period to be the short help, independent how many lines it spans.

Consider the following:
```
import click

@click.group()
def grp():
    pass

@grp.command()
def cmd1():
    """First sentence. Second sentence."""

@grp.command()
def cmd2():
    """Summary line without a period
   
    First real sentence. Another sentence.
    """
```
```
$ python3 foo.py  --help
Usage: foo.py [OPTIONS] COMMAND [ARGS]...

Options:
  --help  Show this message and exit.

Commands:
  cmd1  First sentence.
  cmd2  Summary line without a period First real...
```
To me it would feel more natural:
1. to take the first line as short help, independent of punctuation OR
1. to look for an ending of a phrase or linebreak, whichever comes first.

Both would allow for summary lines without the requirement to end in a period. While this would violate PEP257, I would consider it a gray area, as that summary line is used as short help in a list of commands and I have a use case where a company style guide requires such lists **not** to end in periods.

I'd be happy to provide a pull request for either solution, if it would be accepted.

Pull request:
Short help with summary line
Automatically generated short help messages will stop at the first double linebreak which indicates a summary line. Sometimes the summary line does not have a period, so short help messages end up malformed.

- Fixes #1082

Checklist:

- [x] Add tests that demonstrate the correct behavior of the change. Tests should fail without the change.
- [x] Add or update relevant docs, in the docs folder and in code.
- [x] Add an entry in `CHANGES.rst` summarizing the change and linking to the issue.
- [x] Add `.. versionchanged::` entries in any relevant code docs.
- [x] Run `pre-commit` hooks and fix any issues.
- [x] Run `pytest` and `tox`, no tests failed.

Patch:
diff --git a/CHANGES.rst b/CHANGES.rst
index c11d910..d86e7ce 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -147,6 +147,8 @@ Unreleased
 -   Fix formatting when ``Command.options_metavar`` is empty. :pr:`1551`
 -   The default value passed to ``prompt`` will be cast to the correct
     type like an input value would be. :pr:`1517`
+-   Automatically generated short help messages will stop at the first
+    ending of a phrase or double linebreak. :issue:`1082`
 
 
 Version 7.1.2
diff --git a/docs/options.rst b/docs/options.rst
index fafa6fd..ccd8bee 100644
--- a/docs/options.rst
+++ b/docs/options.rst
@@ -253,7 +253,6 @@ And on the command line:
 
     invoke(info, args=['--shout'])
     invoke(info, args=['--no-shout'])
-    invoke(info)
 
 If you really don't want an off-switch, you can just define one and
 manually inform Click that something is a flag:
@@ -275,7 +274,6 @@ And on the command line:
 .. click:run::
 
     invoke(info, args=['--shout'])
-    invoke(info)
 
 Note that if a slash is contained in your option already (for instance, if
 you use Windows-style parameters where ``/`` is the prefix character), you
diff --git a/src/click/utils.py b/src/click/utils.py
index 0bff5c0..dfb84ce 100644
--- a/src/click/utils.py
+++ b/src/click/utils.py
@@ -49,6 +49,9 @@ def make_str(value):
 
 def make_default_short_help(help, max_length=45):
     """Return a condensed version of help string."""
+    line_ending = help.find("\n\n")
+    if line_ending != -1:
+        help = help[:line_ending]
     words = help.split()
     total_length = 0
     result = []


Test patch:
diff --git a/tests/test_basic.py b/tests/test_basic.py
index de9b0ac..4735627 100644
--- a/tests/test_basic.py
+++ b/tests/test_basic.py
@@ -528,3 +528,22 @@ def test_hidden_group(runner):
     assert result.exit_code == 0
     assert "subgroup" not in result.output
     assert "nope" not in result.output
+
+
+def test_summary_line(runner):
+    @click.group()
+    def cli():
+        pass
+
+    @cli.command()
+    def cmd():
+        """
+        Summary line without period
+
+        Here is a sentence. And here too.
+        """
+        pass
+
+    result = runner.invoke(cli, ["--help"])
+    assert "Summary line without period" in result.output
+    assert "Here is a sentence." not in result.output