Repository: pdm-project/pdm

Issue:
pdm always crashes on a poetry project with names including comma
- [x] I have searched the issue tracker and believe that this is not a duplicate.

**Make sure you run commands with `-v` flag before pasting the output.**

## Steps to reproduce

On my test project with pyproject.toml of which name contains any comma(,), e.g.:

```toml
[tool.poetry]
name = "test"
version = "1.0.0"
descrption = "test project"
authors = ["Min-Gyu, Kim <alsrbbk@gmail.com>"]

[tool.poetry.dependencies]
python = "~3.11.6"
```

pdm command-line execution always crashes.

## Actual behavior

I got a main error when executing any CLI commands.

It seems to be due to a regex, unmatching commas.

Example as follows:

```sh
$ pdm
Traceback (most recent call last):
  File "/home/alsrbbk/.local/bin/pdm", line 8, in <module>
    sys.exit(main())
             ^^^^^^
  File "/home/alsrbbk/.local/share/pdm/venv/lib/python3.11/site-packages/pdm/core.py", line 288, in main
    return Core().main(args or sys.argv[1:])
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/alsrbbk/.local/share/pdm/venv/lib/python3.11/site-packages/pdm/core.py", line 181, in main
    args = self._get_cli_args(args, obj)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/alsrbbk/.local/share/pdm/venv/lib/python3.11/site-packages/pdm/core.py", line 163, in _get_cli_args
    config = project.pyproject.settings.get("options", {})
             ^^^^^^^^^^^^^^^^^
  File "/home/alsrbbk/.pyenv/versions/3.11.6/lib/python3.11/functools.py", line 1001, in __get__
    val = self.func(instance)
          ^^^^^^^^^^^^^^^^^^^
  File "/home/alsrbbk/.local/share/pdm/venv/lib/python3.11/site-packages/pdm/project/core.py", line 112, in pyproject
    return PyProject(self.root / self.PYPROJECT_FILENAME, ui=self.core.ui)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/alsrbbk/.local/share/pdm/venv/lib/python3.11/site-packages/pdm/project/toml_file.py", line 18, in __init__
    self._data = self.read()
                 ^^^^^^^^^^^
  File "/home/alsrbbk/.local/share/pdm/venv/lib/python3.11/site-packages/pdm/project/project_file.py", line 33, in read
    metadata, settings = converter.convert(None, self._path, None)
                         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/alsrbbk/.local/share/pdm/venv/lib/python3.11/site-packages/pdm/formats/poetry.py", line 213, in convert
    return converter.convert()
           ^^^^^^^^^^^^^^^^^^^
  File "/home/alsrbbk/.local/share/pdm/venv/lib/python3.11/site-packages/pdm/formats/base.py", line 85, in convert
    raise MetaConvertError(errors, data=self._data, settings=self.settings)
pdm.formats.base.MetaConvertError: 
authors: 'NoneType' object has no attribute 'groupdict'
```

## Expected behavior

`$ pdm` execution succeeded and show a help page.

## Environment Information

(cannot launch CLI)


Discussion:

Patch:
diff --git a/docs/dev/changelog.md b/docs/dev/changelog.md
index 935d41b5..3591368b 100644
--- a/docs/dev/changelog.md
+++ b/docs/dev/changelog.md
@@ -3,4 +3,4 @@
 !!! warning "Attention"
     Major and minor releases also include changes listed within prior beta releases.
 
---8<-- "../CHANGELOG.md"
+--8<-- "CHANGELOG.md"
diff --git a/docs/dev/contributing.md b/docs/dev/contributing.md
index 3f2d90d8..ea38c9bf 100644
--- a/docs/dev/contributing.md
+++ b/docs/dev/contributing.md
@@ -1,1 +1,1 @@
---8<-- "../CONTRIBUTING.md"
+--8<-- "CONTRIBUTING.md"
diff --git a/docs/usage/dependency.md b/docs/usage/dependency.md
index d3732514..aea94704 100644
--- a/docs/usage/dependency.md
+++ b/docs/usage/dependency.md
@@ -1,6 +1,6 @@
 # Manage Dependencies
 
-PDM provides a bunch of handful commands to help manage your project and dependencies.
+PDM provides a bunch of useful commands to help manage your project and dependencies.
 The following examples are run on Ubuntu 18.04, a few changes must be done if you are using Windows.
 
 ## Add dependencies
diff --git a/news/2665.bugfix.md b/news/2665.bugfix.md
new file mode 100644
index 00000000..e1acc54d
--- /dev/null
+++ b/news/2665.bugfix.md
@@ -0,0 +1,2 @@
+Fix errors when parsing poetry format that contains special characters in author name.
+Poetry-specific `parse_name_email` and `NAME_EMAIL_RE` moved from `pdm.formats.base` to `pdm.formats.poetry`.
diff --git a/news/2740.doc.md b/news/2740.doc.md
new file mode 100644
index 00000000..92d56a02
--- /dev/null
+++ b/news/2740.doc.md
@@ -0,0 +1,1 @@
+Fixed a small non-code typo in docs and prodivded better wording.
diff --git a/src/pdm/formats/base.py b/src/pdm/formats/base.py
index af4fa72e..108ef06f 100644
--- a/src/pdm/formats/base.py
+++ b/src/pdm/formats/base.py
@@ -1,6 +1,5 @@
 from __future__ import annotations
 
-import re
 from typing import Any, Callable, Mapping, TypeVar, cast
 
 import tomlkit
@@ -86,9 +85,6 @@ class MetaConverter(metaclass=_MetaConverterMeta):
         return self._data, self.settings
 
 
-NAME_EMAIL_RE = re.compile(r"(?P<name>[^,]+?)\s*(?:<(?P<email>.+)>)?\s*$")
-
-
 def make_inline_table(data: Mapping) -> dict:
     """Create an inline table from the given data."""
     table = cast(dict, tomlkit.inline_table())
@@ -106,16 +102,3 @@ def make_array(data: list, multiline: bool = False) -> list:
 
 def array_of_inline_tables(value: list[Mapping], multiline: bool = True) -> list[str]:
     return make_array([make_inline_table(item) for item in value], multiline)
-
-
-def parse_name_email(name_email: list[str]) -> list[str]:
-    return array_of_inline_tables(
-        [
-            {
-                k: v
-                for k, v in NAME_EMAIL_RE.match(item).groupdict().items()  # type: ignore[union-attr]
-                if v is not None
-            }
-            for item in name_email
-        ]
-    )
diff --git a/src/pdm/formats/poetry.py b/src/pdm/formats/poetry.py
index c05463e5..440bb9cb 100644
--- a/src/pdm/formats/poetry.py
+++ b/src/pdm/formats/poetry.py
@@ -11,10 +11,10 @@ from pdm.compat import tomllib
 from pdm.formats.base import (
     MetaConverter,
     Unset,
+    array_of_inline_tables,
     convert_from,
     make_array,
     make_inline_table,
-    parse_name_email,
 )
 from pdm.models.markers import Marker, get_marker
 from pdm.models.requirements import Requirement
@@ -93,6 +93,27 @@ def _convert_req(name: str, req_dict: RequirementDict | list[RequirementDict]) -
         yield Requirement.from_req_dict(name, req_dict).as_line()
 
 
+# See https://github.com/python-poetry/poetry-core/pull/521#issuecomment-1327689551
+# for reasoning why email.utils.parseaddr is not used here.
+NAME_EMAIL_RE = re.compile(
+    r"^(?P<name>[- .,\w'’\"():&]+)(?: <(?P<email>.+?)>)?$",  # noqa: RUF001
+    re.UNICODE,
+)
+
+
+def parse_name_email(name_email: list[str]) -> list[str]:
+    return array_of_inline_tables(
+        [
+            {
+                k: v
+                for k, v in NAME_EMAIL_RE.match(item).groupdict().items()  # type: ignore[union-attr]
+                if v is not None
+            }
+            for item in name_email
+        ]
+    )
+
+
 class PoetryMetaConverter(MetaConverter):
     @convert_from("authors")
     def authors(self, value: list[str]) -> list[str]:


Test patch:
diff --git a/tests/fixtures/pyproject.toml b/tests/fixtures/pyproject.toml
index 03d7efca..644fcbfc 100644
--- a/tests/fixtures/pyproject.toml
+++ b/tests/fixtures/pyproject.toml
@@ -3,7 +3,8 @@ name = "poetry"
 version = "1.0.0"
 description = "Python dependency management and packaging made easy."
 authors = [
-    "Sébastien Eustace <sebastien@eustace.io>"
+    "Sébastien Eustace <sebastien@eustace.io>",
+    "Example, Inc. <inc@example.com>"
 ]
 license = "MIT"
 
diff --git a/tests/test_formats.py b/tests/test_formats.py
index 2ac06e85..c72f4db0 100644
--- a/tests/test_formats.py
+++ b/tests/test_formats.py
@@ -77,10 +77,16 @@ def test_convert_poetry(project):
     with cd(FIXTURES):
         result, settings = poetry.convert(project, golden_file, ns())
 
-    assert result["authors"][0] == {
-        "name": "Sébastien Eustace",
-        "email": "sebastien@eustace.io",
-    }
+    assert result["authors"] == [
+        {
+            "name": "Sébastien Eustace",
+            "email": "sebastien@eustace.io",
+        },
+        {
+            "name": "Example, Inc.",
+            "email": "inc@example.com",
+        },
+    ]
     assert result["name"] == "poetry"
     assert result["version"] == "1.0.0"
     assert result["license"] == {"text": "MIT"}