Skip to content

Commit

Permalink
feat: pdm init template argument
Browse files Browse the repository at this point in the history
Signed-off-by: Frost Ming <[email protected]>
  • Loading branch information
frostming committed Jun 25, 2023
1 parent 81f7fc4 commit 05e8648
Show file tree
Hide file tree
Showing 16 changed files with 506 additions and 185 deletions.
15 changes: 14 additions & 1 deletion pdm.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ dependencies = [
"installer<0.8,>=0.7",
"cachecontrol[filecache]>=0.13.0",
"tomli>=1.1.0; python_version < \"3.11\"",
"importlib-resources>=5; python_version < \"3.9\"",
"importlib-metadata>=3.6; python_version < \"3.10\"",
]
readme = "README.md"
Expand Down
211 changes: 89 additions & 122 deletions src/pdm/cli/commands/init.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
from __future__ import annotations

import argparse
from pathlib import Path
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, Any

from pdm import termui
from pdm.cli import actions
from pdm.cli.commands.base import BaseCommand
from pdm.cli.hooks import HookManager
from pdm.cli.options import skip_option
from pdm.cli.templates import ProjectTemplate
from pdm.models.backends import _BACKENDS, DEFAULT_BACKEND, BuildBackend, get_backend
from pdm.models.python import PythonInfo
from pdm.models.specifiers import get_specifier
Expand All @@ -25,23 +25,66 @@ class Command(BaseCommand):
def __init__(self) -> None:
self.interactive = True

@staticmethod
def do_init(
project: Project,
name: str = "",
version: str = "",
description: str = "",
license: str = "MIT",
author: str = "",
email: str = "",
python_requires: str = "",
build_backend: type[BuildBackend] | None = None,
hooks: HookManager | None = None,
self, project: Project, metadata: dict[str, Any], hooks: HookManager, options: argparse.Namespace
) -> None:
"""Bootstrap the project and create a pyproject.toml"""
with ProjectTemplate(options.template) as template:
template.generate(project.root, metadata)
project.pyproject.reload()
hooks.try_emit("post_init")

def set_interactive(self, value: bool) -> None:
self.interactive = value

def ask(self, question: str, default: str) -> str:
if not self.interactive:
return default
return termui.ask(question, default=default)

def get_metadata_from_input(self, project: Project, options: argparse.Namespace) -> dict[str, Any]:
from pdm.formats.base import array_of_inline_tables, make_array, make_inline_table

hooks = hooks or HookManager(project)
is_library = options.lib
if not is_library and self.interactive:
is_library = termui.confirm(
"Is the project a library that is installable?\n"
"If yes, we will need to ask a few more questions to include "
"the project name and build backend"
)
build_backend: type[BuildBackend] | None = None
if is_library:
name = self.ask("Project name", project.root.name)
version = self.ask("Project version", "0.1.0")
description = self.ask("Project description", "")
if options.backend:
build_backend = get_backend(options.backend)
elif self.interactive:
all_backends = list(_BACKENDS)
project.core.ui.echo("Which build backend to use?")
for i, backend in enumerate(all_backends):
project.core.ui.echo(f"{i}. [success]{backend}[/]")
selected_backend = termui.ask(
"Please select",
prompt_type=int,
choices=[str(i) for i in range(len(all_backends))],
show_choices=False,
default=0,
)
build_backend = get_backend(all_backends[int(selected_backend)])
else:
build_backend = DEFAULT_BACKEND
else:
name, version, description = "", "", ""
license = self.ask("License(SPDX name)", "MIT")

git_user, git_email = get_user_email_from_git()
author = self.ask("Author name", git_user)
email = self.ask("Author email", git_email)
python = project.python
python_version = f"{python.major}.{python.minor}"
python_requires = self.ask("Python requires('*' to allow any)", f">={python_version}")

data = {
"project": {
"name": name,
Expand All @@ -52,50 +95,14 @@ def do_init(
"dependencies": make_array([], True),
},
}
if build_backend is not None:
data["build-system"] = build_backend.build_system()

if python_requires and python_requires != "*":
get_specifier(python_requires)
data["project"]["requires-python"] = python_requires
if name and version:
readme = next(project.root.glob("README*"), None)
if readme is None:
readme = project.root.joinpath("README.md")
readme.write_text(f"# {name}\n\n{description}\n", encoding="utf-8")
data["project"]["readme"] = readme.name
get_specifier(python_requires)
project.pyproject._data.update(data)
project.pyproject.write()
Command._write_gitignore(project.root.joinpath(".gitignore"))
hooks.try_emit("post_init")

@staticmethod
def _write_gitignore(path: Path) -> None:
import requests

url = "https://raw.githubusercontent.com/github/gitignore/master/Python.gitignore"
if not path.exists():
try:
resp = requests.get(url, timeout=5)
resp.raise_for_status()
except requests.exceptions.RequestException:
content = "\n".join(["build/", "dist/", "*.egg-info/", "__pycache__/", "*.py[cod]"]) + "\n"
else:
content = resp.text
content += ".pdm-python\n"
else:
content = path.read_text(encoding="utf-8")
if ".pdm-python" in content:
return
content += ".pdm-python\n"
path.write_text(content, encoding="utf-8")

def set_interactive(self, value: bool) -> None:
self.interactive = value
if build_backend is not None:
data["build-system"] = build_backend.build_system()

def ask(self, question: str, default: str) -> str:
if not self.interactive:
return default
return termui.ask(question, default=default)
return data

def add_arguments(self, parser: argparse.ArgumentParser) -> None:
skip_option.add_to_parser(parser)
Expand All @@ -108,110 +115,70 @@ def add_arguments(self, parser: argparse.ArgumentParser) -> None:
parser.add_argument("--python", help="Specify the Python version/path to use")
parser.add_argument("--backend", choices=list(_BACKENDS), help="Specify the build backend")
parser.add_argument("--lib", action="store_true", help="Create a library project")
parser.add_argument(
"template", nargs="?", help="Specify the project template, which can be a local path or a Git URL"
)
parser.set_defaults(search_parent=False)

def handle(self, project: Project, options: argparse.Namespace) -> None:
def set_python(self, project: Project, python: str | None, hooks: HookManager) -> None:
from pdm.cli.commands.use import Command as UseCommand

hooks = HookManager(project, options.skip)
if project.pyproject.exists():
project.core.ui.echo("pyproject.toml already exists, update it now.", style="primary")
else:
project.core.ui.echo("Creating a pyproject.toml for PDM...", style="primary")
self.set_interactive(not options.non_interactive)
do_use = UseCommand().do_use
if self.interactive:
python = do_use(
python_info = do_use(
project,
options.python or "",
first=bool(options.python),
python or "",
first=bool(python),
ignore_remembered=True,
ignore_requires_python=True,
save=False,
hooks=hooks,
)
else:
python = do_use(
python_info = do_use(
project,
options.python or "3",
python or "3",
first=True,
ignore_remembered=True,
ignore_requires_python=True,
save=False,
hooks=hooks,
)
if project.config["python.use_venv"] and python.get_venv() is None:
if project.config["python.use_venv"] and python_info.get_venv() is None:
if not self.interactive or termui.confirm(
f"Would you like to create a virtualenv with [success]{python.executable}[/]?",
f"Would you like to create a virtualenv with [success]{python_info.executable}[/]?",
default=True,
):
project._python = python
project._python = python_info
try:
path = project._create_virtualenv()
python = PythonInfo.from_path(get_venv_python(path))
python_info = PythonInfo.from_path(get_venv_python(path))
except Exception as e: # pragma: no cover
project.core.ui.echo(
f"Error occurred when creating virtualenv: {e}\nPlease fix it and create later.",
style="error",
err=True,
)
if python.get_venv() is None:
if python_info.get_venv() is None:
project.core.ui.echo(
"You are using the PEP 582 mode, no virtualenv is created.\n"
"For more info, please visit https://peps.python.org/pep-0582/",
style="success",
)
is_library = options.lib
if not is_library and self.interactive:
is_library = termui.confirm(
"Is the project a library that is installable?\n"
"If yes, we will need to ask a few more questions to include "
"the project name and build backend"
)
build_backend: type[BuildBackend] | None = None
if is_library:
name = self.ask("Project name", project.root.name)
version = self.ask("Project version", "0.1.0")
description = self.ask("Project description", "")
if options.backend:
build_backend = get_backend(options.backend)
elif self.interactive:
all_backends = list(_BACKENDS)
project.core.ui.echo("Which build backend to use?")
for i, backend in enumerate(all_backends):
project.core.ui.echo(f"{i}. [success]{backend}[/]")
selected_backend = termui.ask(
"Please select",
prompt_type=int,
choices=[str(i) for i in range(len(all_backends))],
show_choices=False,
default=0,
)
build_backend = get_backend(all_backends[int(selected_backend)])
else:
build_backend = DEFAULT_BACKEND
project.python = python_info

def handle(self, project: Project, options: argparse.Namespace) -> None:
hooks = HookManager(project, options.skip)
if project.pyproject.exists():
project.core.ui.echo("pyproject.toml already exists, update it now.", style="primary")
else:
name, version, description = "", "", ""
license = self.ask("License(SPDX name)", "MIT")
project.core.ui.echo("Creating a pyproject.toml for PDM...", style="primary")
self.set_interactive(not options.non_interactive)

git_user, git_email = get_user_email_from_git()
author = self.ask("Author name", git_user)
email = self.ask("Author email", git_email)
python_version = f"{python.major}.{python.minor}"
python_requires = self.ask("Python requires('*' to allow any)", f">={python_version}")
project.python = python

self.do_init(
project,
name=name,
version=version,
description=description,
license=license,
author=author,
email=email,
python_requires=python_requires,
build_backend=build_backend,
hooks=hooks,
)
self.set_python(project, options.python, hooks)

metadata = self.get_metadata_from_input(project, options)
self.do_init(project, metadata, hooks=hooks, options=options)
project.core.ui.echo("Project is initialized successfully", style="primary")
if self.interactive:
actions.ask_for_import(project)
1 change: 1 addition & 0 deletions src/pdm/cli/completions/pdm.zsh
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,7 @@ _pdm() {
'--backend[Specify the build backend]:backend:(pdm-backend setuptools hatchling flit pdm-pep517)'
'--lib[Create a library project]'
'--python[Specify the Python version/path to use]:python:'
'1:filename:_files'
)
;;
install)
Expand Down
Loading

0 comments on commit 05e8648

Please sign in to comment.