Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Open
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
90 changes: 6 additions & 84 deletions commitizen/commands/init.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
from __future__ import annotations

import os
import shutil
from typing import Any, NamedTuple

import questionary
import yaml

from commitizen import cmd, factory, out
from commitizen import cmd, factory, out, project_info
from commitizen.__version__ import __version__
from commitizen.config import BaseConfig, JsonConfig, TomlConfig, YAMLConfig
from commitizen.cz import registry
Expand Down Expand Up @@ -65,65 +63,13 @@ def title(self) -> str:
)


class ProjectInfo:
"""Discover information about the current folder."""

@property
def has_pyproject(self) -> bool:
return os.path.isfile("pyproject.toml")

@property
def has_uv_lock(self) -> bool:
return os.path.isfile("uv.lock")

@property
def has_setup(self) -> bool:
return os.path.isfile("setup.py")

@property
def has_pre_commit_config(self) -> bool:
return os.path.isfile(".pre-commit-config.yaml")

@property
def is_python_uv(self) -> bool:
return self.has_pyproject and self.has_uv_lock

@property
def is_python_poetry(self) -> bool:
if not self.has_pyproject:
return False
with open("pyproject.toml") as f:
return "[tool.poetry]" in f.read()

@property
def is_python(self) -> bool:
return self.has_pyproject or self.has_setup

@property
def is_rust_cargo(self) -> bool:
return os.path.isfile("Cargo.toml")

@property
def is_npm_package(self) -> bool:
return os.path.isfile("package.json")

@property
def is_php_composer(self) -> bool:
return os.path.isfile("composer.json")

@property
def is_pre_commit_installed(self) -> bool:
return bool(shutil.which("pre-commit"))


class Init:
_PRE_COMMIT_CONFIG_PATH = ".pre-commit-config.yaml"

def __init__(self, config: BaseConfig, *args: object) -> None:
self.config: BaseConfig = config
self.encoding = config.settings["encoding"]
self.cz = factory.committer_factory(self.config)
self.project_info = ProjectInfo()

def __call__(self) -> None:
if self.config.path:
Expand Down Expand Up @@ -195,14 +141,10 @@ def __call__(self) -> None:
out.success("Configuration complete 🚀")

def _ask_config_path(self) -> str:
default_path = (
"pyproject.toml" if self.project_info.has_pyproject else ".cz.toml"
)

name: str = questionary.select(
"Please choose a supported config file: ",
choices=CONFIG_FILES,
default=default_path,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The suffix path can sometimes refer to Path objects. I consider it less confusing with the suffix filename to indicate that it is a string.

default=project_info.get_default_config_filename(),
style=self.cz.style,
).unsafe_ask()
return name
Expand Down Expand Up @@ -267,37 +209,17 @@ def _ask_version_provider(self) -> str:
"Choose the source of the version:",
choices=_VERSION_PROVIDER_CHOICES,
style=self.cz.style,
default=self._default_version_provider,
default=project_info.get_default_version_provider(),
).unsafe_ask()
return version_provider

@property
def _default_version_provider(self) -> str:
if self.project_info.is_python:
if self.project_info.is_python_poetry:
return "poetry"
if self.project_info.is_python_uv:
return "uv"
return "pep621"

if self.project_info.is_rust_cargo:
return "cargo"
if self.project_info.is_npm_package:
return "npm"
if self.project_info.is_php_composer:
return "composer"

return "commitizen"

def _ask_version_scheme(self) -> str:
"""Ask for setting: version_scheme"""
default_scheme = "pep440" if self.project_info.is_python else "semver"

scheme: str = questionary.select(
"Choose version scheme: ",
choices=KNOWN_SCHEMES,
style=self.cz.style,
default=default_scheme,
default=project_info.get_default_version_scheme(),
).unsafe_ask()
return scheme

Expand Down Expand Up @@ -351,7 +273,7 @@ def _get_config_data(self) -> dict[str, Any]:
],
}

if not self.project_info.has_pre_commit_config:
if not project_info.has_pre_commit_config():
# .pre-commit-config.yaml does not exist
return {"repos": [CZ_HOOK_CONFIG]}

Expand All @@ -377,7 +299,7 @@ def _install_pre_commit_hook(self, hook_types: list[str] | None = None) -> None:
) as config_file:
yaml.safe_dump(config_data, stream=config_file)

if not self.project_info.is_pre_commit_installed:
if not project_info.is_pre_commit_installed():
raise InitFailedError("pre-commit is not installed in current environment.")
if hook_types is None:
hook_types = ["commit-msg", "pre-push"]
Expand Down
75 changes: 75 additions & 0 deletions commitizen/project_info.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
"""Resolves project information about the current working directory."""

import shutil
from pathlib import Path
from typing import Literal


def has_pyproject() -> bool:
return Path("pyproject.toml").is_file()


def has_uv_lock() -> bool:
return Path("uv.lock").is_file()


def has_setup() -> bool:
return Path("setup.py").is_file()


def has_pre_commit_config() -> bool:
return Path(".pre-commit-config.yaml").is_file()


def is_python_uv() -> bool:
return has_pyproject() and has_uv_lock()


def is_python_poetry() -> bool:
return has_pyproject() and "[tool.poetry]" in Path("pyproject.toml").read_text()


def is_python() -> bool:
return has_pyproject() or has_setup()


def is_rust_cargo() -> bool:
return Path("Cargo.toml").is_file()


def is_npm_package() -> bool:
return Path("package.json").is_file()


def is_php_composer() -> bool:
return Path("composer.json").is_file()


def is_pre_commit_installed() -> bool:
return bool(shutil.which("pre-commit"))


def get_default_version_provider() -> Literal[
"commitizen", "cargo", "composer", "npm", "pep621", "poetry", "uv"
]:
if is_python():
if is_python_poetry():
return "poetry"
if is_python_uv():
return "uv"
return "pep621"
if is_rust_cargo():
return "cargo"
if is_npm_package():
return "npm"
if is_php_composer():
return "composer"
return "commitizen"


def get_default_config_filename() -> Literal["pyproject.toml", ".cz.toml"]:
return "pyproject.toml" if has_pyproject() else ".cz.toml"


def get_default_version_scheme() -> Literal["pep440", "semver"]:
return "pep440" if is_python() else "semver"
6 changes: 3 additions & 3 deletions tests/commands/test_init_command.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ def test_executed_pre_commit_command(config):
def pre_commit_installed(mocker: MockFixture):
# Assume the `pre-commit` is installed
mocker.patch(
"commitizen.commands.init.ProjectInfo.is_pre_commit_installed",
"commitizen.project_info.is_pre_commit_installed",
return_value=True,
)
# And installation success (i.e. no exception raised)
Expand Down Expand Up @@ -230,7 +230,7 @@ def test_pre_commit_not_installed(
):
# Assume `pre-commit` is not installed
mocker.patch(
"commitizen.commands.init.ProjectInfo.is_pre_commit_installed",
"commitizen.project_info.is_pre_commit_installed",
return_value=False,
)
with tmpdir.as_cwd():
Expand All @@ -242,7 +242,7 @@ def test_pre_commit_exec_failed(
):
# Assume `pre-commit` is installed
mocker.patch(
"commitizen.commands.init.ProjectInfo.is_pre_commit_installed",
"commitizen.project_info.is_pre_commit_installed",
return_value=True,
)
# But pre-commit installation will fail
Expand Down
Loading
Loading