Repository: pallets/click (python)

Issue:
Broken message about invalid argument value for template "File ... is a directory"
**User Actions**
```sh
mkdir $'my\ndir'
my-tool $'my\ndir'
```

**Expected Output**
```
Invalid value for 'PATH': File 'my\ndir' is a directory.
```


**Actual Output**
```
Invalid value for 'PATH': File 'my
dir' is a directory.
```


**Code**
```python
from pathlib import Path
from typing import Annotated

import typer


def main(path: Annotated[Path, typer.Argument(dir_okay=False)]) -> None:
    pass


if __name__ == '__main__':
    typer.run(main)
```

**Cause**
You clearly forgot `!r` on [this](https://github.com/pallets/click/blob/f8857cb03268b5b952b88b2acb3e11d9f0f7b6e4/src/click/types.py#L896) line and are using quotes instead.

Just compare these lines in [click.types](https://github.com/pallets/click/blob/f8857cb03268b5b952b88b2acb3e11d9f0f7b6e4/src/click/types.py#L870-L928):
```python
_("{name} {filename!r} does not exist.").format(
_("{name} {filename!r} is a file.").format(
_("{name} '{filename}' is a directory.").format(
_("{name} {filename!r} is not readable.").format(
_("{name} {filename!r} is not writable.").format(
_("{name} {filename!r} is not executable.").format(
```


Pull request description:
Prevent multiline error message on invalid path
Fixes an issue where an invalid `click.Path` parameter, which contains a `\n`, will result in an error that prints out on multiple lines.

fixes #2697


Patch:
diff --git a/CHANGES.rst b/CHANGES.rst
index 4188303..aba1bbe 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -6,6 +6,9 @@ Version 8.1.8
 Unreleased
 
 -   Fix an issue with type hints for ``click.open_file()``. :issue:`2717`
+-   Fix issue where error message for invalid ``click.Path`` displays on
+    multiple lines. :issue:`2697`
+
 
 Version 8.1.7
 -------------
diff --git a/src/click/types.py b/src/click/types.py
index dc52bac..c310377 100644
--- a/src/click/types.py
+++ b/src/click/types.py
@@ -892,7 +892,7 @@ class Path(ParamType):
                 )
             if not self.dir_okay and stat.S_ISDIR(st.st_mode):
                 self.fail(
-                    _("{name} '{filename}' is a directory.").format(
+                    _("{name} {filename!r} is a directory.").format(
                         name=self.name.title(), filename=format_filename(value)
                     ),
                     param,


Test patch:
diff --git a/tests/test_types.py b/tests/test_types.py
index 0a25088..79068e1 100644
--- a/tests/test_types.py
+++ b/tests/test_types.py
@@ -1,5 +1,6 @@
 import os.path
 import pathlib
+import platform
 import tempfile
 
 import pytest
@@ -232,3 +233,14 @@ def test_file_surrogates(type, tmp_path):
 def test_file_error_surrogates():
     message = FileError(filename="\udcff").format_message()
     assert message == "Could not open file '�': unknown error"
+
+
+@pytest.mark.skipif(
+    platform.system() == "Windows", reason="Filepath syntax differences."
+)
+def test_invalid_path_with_esc_sequence():
+    with pytest.raises(click.BadParameter) as exc_info:
+        with tempfile.TemporaryDirectory(prefix="my\ndir") as tempdir:
+            click.Path(dir_okay=False).convert(tempdir, None, None)
+
+    assert "my\\ndir" in exc_info.value.message