Skip to content
Draft
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
76 changes: 61 additions & 15 deletions src/poetry/console/commands/init.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@

from cleo.helpers import option
from packaging.utils import canonicalize_name
from poetry.core.utils.patterns import AUTHOR_REGEX
from tomlkit import array
from tomlkit import inline_table

from poetry.console.commands.command import Command
Expand Down Expand Up @@ -39,7 +41,13 @@ class InitCommand(Command):
options: ClassVar[list[Option]] = [
option("name", None, "Name of the package.", flag=False),
option("description", None, "Description of the package.", flag=False),
option("author", None, "Author name of the package.", flag=False),
option(
"author",
None,
"Author name of the package.",
flag=False,
multiple=True,
),
option("python", None, "Compatible Python versions.", flag=False),
option(
"dependency",
Expand Down Expand Up @@ -148,21 +156,42 @@ def _init_pyproject(
if not description and is_interactive:
description = self.ask(self.create_question("Description []: ", default=""))

author = self.option("author")
if not author and vcs_config.get("user.name"):
author = vcs_config["user.name"]
author_email = vcs_config.get("user.email")
if author_email:
author += f" <{author_email}>"

if is_interactive:
question = self.create_question(
f"Author [<comment>{author}</comment>, n to skip]: ", default=author
raw_option_authors = self.option("author")
if raw_option_authors is None:
option_authors: list[str] = []
elif isinstance(raw_option_authors, str):
option_authors = [raw_option_authors]
else:
option_authors = list(raw_option_authors)

option_authors = [
author
for author in (
self._validate_author(author, "") for author in option_authors
)
question.set_validator(lambda v: self._validate_author(v, author))
author = self.ask(question)
if author
]
authors: list[str] = []

if len(option_authors) > 1 or (option_authors and not is_interactive):
authors = option_authors
else:
author = option_authors[0] if option_authors else None
if not author and vcs_config.get("user.name"):
author = vcs_config["user.name"]
author_email = vcs_config.get("user.email")
if author_email:
author += f" <{author_email}>"

authors = [author] if author else []
if is_interactive:
question = self.create_question(
f"Author [<comment>{author}</comment>, n to skip]: ",
default=author,
)
question.set_validator(lambda v: self._validate_author(v, author or ""))
author = self.ask(question)

authors = [author] if author else []

license_name = self.option("license")
if not license_name and is_interactive:
Expand Down Expand Up @@ -253,6 +282,11 @@ def _init_pyproject(
layout_.create(project_path, with_pyproject=False)

content = layout_.generate_project_content(project_path)
if len(authors) > 1:
content["project"]["authors"] = array().multiline(True)
for author in authors:
content["project"]["authors"].append(self._format_author(author))

for section, item in content.items():
pyproject.data.append(section, item)

Expand Down Expand Up @@ -498,7 +532,6 @@ def _format_requirements(self, requirements: list[dict[str, str]]) -> Requiremen
@staticmethod
def _validate_author(author: str, default: str) -> str | None:
from poetry.core.utils.helpers import combine_unicode
from poetry.core.utils.patterns import AUTHOR_REGEX

author = combine_unicode(author or default)

Expand All @@ -514,6 +547,19 @@ def _validate_author(author: str, default: str) -> str | None:

return author

@staticmethod
def _format_author(author: str) -> dict[str, str]:
m = AUTHOR_REGEX.match(author)
if m is None:
# This should not happen because author has been validated before.
raise ValueError(f"Invalid author: {author}")

formatted_author = {"name": m.group("name")}
if email := m.group("email"):
formatted_author["email"] = email

return formatted_author

@staticmethod
def _validate_package(package: str | None) -> str | None:
if package and len(package.split()) > 2:
Expand Down
27 changes: 27 additions & 0 deletions tests/console/commands/test_init.py
Original file line number Diff line number Diff line change
Expand Up @@ -972,6 +972,33 @@ def test_init_non_interactive_existing_pyproject_add_dependency(
)


def test_init_non_interactive_with_multiple_authors(
tester: CommandTester, source_dir: Path
) -> None:
tester.execute(
"--author 'Alice Example <alice@example.com>' "
"--author 'Bob Example <bob@example.com>' "
"--name 'my-package' "
"--python '>=3.12'",
interactive=False,
)

expected = """\
[project]
name = "my-package"
version = "0.1.0"
description = ""
authors = [
{name = "Alice Example",email = "alice@example.com"},
{name = "Bob Example",email = "bob@example.com"},
]
requires-python = ">=3.12"
dependencies = [
]
"""
assert expected in (source_dir / "pyproject.toml").read_text(encoding="utf-8")


def test_init_existing_pyproject_with_build_system_fails(
tester: CommandTester, source_dir: Path, init_basic_inputs: str
) -> None:
Expand Down
Loading