diff --git a/news/2163.feature.md b/news/2163.feature.md new file mode 100644 index 0000000000..df2593e18c --- /dev/null +++ b/news/2163.feature.md @@ -0,0 +1 @@ +Add an `--overwrite` option to `pdm init` to overwrite existing files(default False). diff --git a/src/pdm/cli/commands/init.py b/src/pdm/cli/commands/init.py index 2b1eaaacdc..18db20b004 100644 --- a/src/pdm/cli/commands/init.py +++ b/src/pdm/cli/commands/init.py @@ -76,7 +76,7 @@ def _init_cookiecutter(self, project: Project, options: argparse.Namespace) -> N def _init_builtin(self, project: Project, options: argparse.Namespace) -> None: metadata = self.get_metadata_from_input(project, options) with ProjectTemplate(options.template) as template: - template.generate(project.root, metadata) + template.generate(project.root, metadata, options.overwrite) project.pyproject.reload() def set_interactive(self, value: bool) -> None: @@ -185,6 +185,7 @@ def add_arguments(self, parser: argparse.ArgumentParser) -> None: "template", nargs="?", help="Specify the project template, which can be a local path or a Git URL" ) parser.add_argument("generator_args", nargs=argparse.REMAINDER, help="Arguments passed to the generator") + parser.add_argument("-r", "--overwrite", action="store_true", help="Overwrite existing files") parser.set_defaults(search_parent=False, generator="builtin") def set_python(self, project: Project, python: str | None, hooks: HookManager) -> None: diff --git a/src/pdm/cli/completions/pdm.bash b/src/pdm/cli/completions/pdm.bash index 1a74d78bce..aa3c5028f0 100644 --- a/src/pdm/cli/completions/pdm.bash +++ b/src/pdm/cli/completions/pdm.bash @@ -65,7 +65,7 @@ _pdm_a919b69078acdf0a_complete() ;; (init) - opts="--backend --cookiecutter --copier --global --help --lib --non-interactive --project --python --skip --verbose" + opts="--backend --cookiecutter --copier --global --help --lib --non-interactive --overwrite --project --python --skip --verbose" ;; (install) diff --git a/src/pdm/cli/completions/pdm.fish b/src/pdm/cli/completions/pdm.fish index dbd32a06a4..cc3cf7ea20 100644 --- a/src/pdm/cli/completions/pdm.fish +++ b/src/pdm/cli/completions/pdm.fish @@ -157,6 +157,7 @@ complete -c pdm -A -n '__fish_seen_subcommand_from init' -l global -d 'Use the g complete -c pdm -A -n '__fish_seen_subcommand_from init' -l help -d 'Show this help message and exit.' complete -c pdm -A -n '__fish_seen_subcommand_from init' -l lib -d 'Create a library project' complete -c pdm -A -n '__fish_seen_subcommand_from init' -l non-interactive -d 'Don\'t ask questions but use default values' +complete -c pdm -A -n '__fish_seen_subcommand_from init' -l overwrite -d 'Overwrite existing files' complete -c pdm -A -n '__fish_seen_subcommand_from init' -l project -d 'Specify another path as the project root, which changes the base of pyproject.toml and __pypackages__ [env var: PDM_PROJECT]' complete -c pdm -A -n '__fish_seen_subcommand_from init' -l python -d 'Specify the Python version/path to use' complete -c pdm -A -n '__fish_seen_subcommand_from init' -l skip -d 'Skip some tasks and/or hooks by their comma-separated names. Can be supplied multiple times. Use ":all" to skip all hooks. Use ":pre" and ":post" to skip all pre or post hooks.' diff --git a/src/pdm/cli/completions/pdm.ps1 b/src/pdm/cli/completions/pdm.ps1 index 9e10322431..13aa516401 100644 --- a/src/pdm/cli/completions/pdm.ps1 +++ b/src/pdm/cli/completions/pdm.ps1 @@ -282,7 +282,7 @@ function TabExpansion($line, $lastWord) { "init" { $completer.AddOpts( @( - [Option]::new(@("-g", "--global", "--non-interactive", "-n", "--python", "--lib", "--copier", "--cookiecutter")), + [Option]::new(@("-g", "--global", "--non-interactive", "-n", "--python", "--lib", "--copier", "--cookiecutter", "--overwrite")), $projectOption, $skipOption, [Option]::new(@("--backend")).WithValues(@("pdm-backend", "setuptools", "flit", "hatching", "pdm-pep517")) diff --git a/src/pdm/cli/completions/pdm.zsh b/src/pdm/cli/completions/pdm.zsh index 4aa38f555f..b8766f5fb2 100644 --- a/src/pdm/cli/completions/pdm.zsh +++ b/src/pdm/cli/completions/pdm.zsh @@ -185,6 +185,7 @@ _pdm() { {-g,--global}'[Use the global project, supply the project root with `-p` option]' {-n,--non-interactive}"[Don't ask questions but use default values]" {-k,--skip}'[Skip some tasks and/or hooks by their comma-separated names]' + {-r,--overwrite}'[Overwrite existing files]' '--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:' diff --git a/src/pdm/cli/templates/__init__.py b/src/pdm/cli/templates/__init__.py index 8b311382ba..3a735262b3 100644 --- a/src/pdm/cli/templates/__init__.py +++ b/src/pdm/cli/templates/__init__.py @@ -34,7 +34,7 @@ def __enter__(self) -> "ProjectTemplate": def __exit__(self, *args: Any) -> None: shutil.rmtree(self._path, ignore_errors=True) - def generate(self, target_path: Path, metadata: dict[str, Any]) -> None: + def generate(self, target_path: Path, metadata: dict[str, Any], overwrite: bool = False) -> None: from pdm.compat import tomllib def replace_all(path: str, old: str, new: str) -> None: @@ -74,7 +74,7 @@ def replace_all(path: str, old: str, new: str) -> None: replace_all(os.path.join(root, f), import_name, new_import_name) target_path.mkdir(exist_ok=True, parents=True) - self.mirror(self._path, target_path, [self._path / "pyproject.toml"]) + self.mirror(self._path, target_path, [self._path / "pyproject.toml"], overwrite=overwrite) self._generate_pyproject(target_path / "pyproject.toml", metadata) def prepare_template(self) -> None: @@ -94,6 +94,8 @@ def mirror( dst: Path, skip: list[ST] | None = None, copyfunc: Callable[[ST, Path], Any] = shutil.copy2, # type: ignore[assignment] + *, + overwrite: bool = False, ) -> None: if skip and src in skip: return @@ -101,7 +103,7 @@ def mirror( dst.mkdir(exist_ok=True) for child in src.iterdir(): ProjectTemplate.mirror(child, dst / child.name, skip, copyfunc) - else: + elif overwrite or not dst.exists(): copyfunc(src, dst) @staticmethod