Skip to content
Closed
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
18 changes: 12 additions & 6 deletions commitizen/providers/base_provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@ class FileProvider(VersionProvider):
def file(self) -> Path:
return Path() / self.filename

def _get_encoding(self) -> str:
return self.config.settings["encoding"]


class JsonProvider(FileProvider):
"""
Expand All @@ -58,13 +61,16 @@ class JsonProvider(FileProvider):
indent: ClassVar[int] = 2

def get_version(self) -> str:
document = json.loads(self.file.read_text())
document = json.loads(self.file.read_text(encoding=self._get_encoding()))
return self.get(document)

def set_version(self, version: str) -> None:
document = json.loads(self.file.read_text())
document = json.loads(self.file.read_text(encoding=self._get_encoding()))
self.set(document, version)
self.file.write_text(json.dumps(document, indent=self.indent) + "\n")
self.file.write_text(
json.dumps(document, indent=self.indent, ensure_ascii=False) + "\n",
encoding=self._get_encoding(),
)

def get(self, document: Mapping[str, str]) -> str:
return document["version"]
Expand All @@ -79,13 +85,13 @@ class TomlProvider(FileProvider):
"""

def get_version(self) -> str:
document = tomlkit.parse(self.file.read_text())
document = tomlkit.parse(self.file.read_text(encoding=self._get_encoding()))
return self.get(document)

def set_version(self, version: str) -> None:
document = tomlkit.parse(self.file.read_text())
document = tomlkit.parse(self.file.read_text(encoding=self._get_encoding()))
self.set(document, version)
self.file.write_text(tomlkit.dumps(document))
self.file.write_text(tomlkit.dumps(document), encoding=self._get_encoding())

def get(self, document: tomlkit.TOMLDocument) -> str:
return document["project"]["version"] # type: ignore[index,return-value]
Expand Down
16 changes: 12 additions & 4 deletions commitizen/providers/cargo_provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,12 @@ def set_version(self, version: str) -> None:
self.set_lock_version(version)

def set_lock_version(self, version: str) -> None:
cargo_toml_content = parse(self.file.read_text())
cargo_lock_content = parse(self.lock_file.read_text())
cargo_toml_content = parse(
self.file.read_text(encoding=self._get_encoding())
)
cargo_lock_content = parse(
self.lock_file.read_text(encoding=self._get_encoding())
)
packages = cargo_lock_content["package"]

if TYPE_CHECKING:
Expand Down Expand Up @@ -75,7 +79,9 @@ def set_lock_version(self, version: str) -> None:
continue

cargo_file = Path(path) / "Cargo.toml"
package_content = parse(cargo_file.read_text()).get("package", {})
package_content = parse(
cargo_file.read_text(encoding=self._get_encoding())
).get("package", {})
if TYPE_CHECKING:
assert isinstance(package_content, dict)
try:
Expand All @@ -92,7 +98,9 @@ def set_lock_version(self, version: str) -> None:
if package["name"] in members_inheriting:
cargo_lock_content["package"][i]["version"] = version # type: ignore[index]

self.lock_file.write_text(dumps(cargo_lock_content))
self.lock_file.write_text(
dumps(cargo_lock_content), encoding=self._get_encoding()
)


def _try_get_workspace(document: TOMLDocument) -> dict:
Expand Down
36 changes: 29 additions & 7 deletions commitizen/providers/npm_provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ class NpmProvider(VersionProvider):
lock_filename = "package-lock.json"
shrinkwrap_filename = "npm-shrinkwrap.json"

def _get_encoding(self) -> str:
return self.config.settings["encoding"]

@property
def package_file(self) -> Path:
return Path() / self.package_filename
Expand All @@ -36,29 +39,48 @@ def get_version(self) -> str:
"""
Get the current version from package.json
"""
package_document = json.loads(self.package_file.read_text())
package_document = json.loads(
self.package_file.read_text(encoding=self._get_encoding())
)
return self.get_package_version(package_document)

def set_version(self, version: str) -> None:
package_document = self.set_package_version(
json.loads(self.package_file.read_text()), version
json.loads(
self.package_file.read_text(encoding=self._get_encoding())
),
version,
)
self.package_file.write_text(
json.dumps(package_document, indent=self.indent) + "\n"
json.dumps(package_document, indent=self.indent, ensure_ascii=False)
+ "\n",
encoding=self._get_encoding(),
)
if self.lock_file.is_file():
lock_document = self.set_lock_version(
json.loads(self.lock_file.read_text()), version
json.loads(
self.lock_file.read_text(encoding=self._get_encoding())
),
version,
)
self.lock_file.write_text(
json.dumps(lock_document, indent=self.indent) + "\n"
json.dumps(lock_document, indent=self.indent, ensure_ascii=False)
+ "\n",
encoding=self._get_encoding(),
)
if self.shrinkwrap_file.is_file():
shrinkwrap_document = self.set_shrinkwrap_version(
json.loads(self.shrinkwrap_file.read_text()), version
json.loads(
self.shrinkwrap_file.read_text(encoding=self._get_encoding())
),
version,
)
self.shrinkwrap_file.write_text(
json.dumps(shrinkwrap_document, indent=self.indent) + "\n"
json.dumps(
shrinkwrap_document, indent=self.indent, ensure_ascii=False
)
+ "\n",
encoding=self._get_encoding(),
)

def get_package_version(self, document: Mapping[str, str]) -> str:
Expand Down
12 changes: 9 additions & 3 deletions commitizen/providers/uv_provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,21 @@ def set_version(self, version: str) -> None:
self.set_lock_version(version)

def set_lock_version(self, version: str) -> None:
pyproject_toml_content = tomlkit.parse(self.file.read_text())
pyproject_toml_content = tomlkit.parse(
self.file.read_text(encoding=self._get_encoding())
)
project_name = pyproject_toml_content["project"]["name"] # type: ignore[index]
normalized_project_name = canonicalize_name(str(project_name))

document = tomlkit.parse(self.lock_file.read_text())
document = tomlkit.parse(
self.lock_file.read_text(encoding=self._get_encoding())
)

packages: tomlkit.items.AoT = document["package"] # type: ignore[assignment]
for i, package in enumerate(packages):
if package["name"] == normalized_project_name:
document["package"][i]["version"] = version # type: ignore[index]
break
self.lock_file.write_text(tomlkit.dumps(document))
self.lock_file.write_text(
tomlkit.dumps(document), encoding=self._get_encoding()
)
6 changes: 6 additions & 0 deletions tests/data/encoding_test_composer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"name": "encoding-test-composer",
"description": "Тест описания для проверки кодировки",
"version": "0.1.0"
}

36 changes: 36 additions & 0 deletions tests/data/encoding_test_pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
[project]
name = "pythonproject-test"
version = "0.4.1"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.12"
dependencies = []


[tool.commitizen]
name = "cz_customize"
tag_format = "v$version"
version_scheme = "pep440"
version_provider = "uv"
update_changelog_on_bump = true
changelog_start_rev = "v1.1.0"

[tool.commitizen.customize]
message_template = "{{ change_type }}{% if scope != 'none' %}({{ scope }}){% endif %}: {{ message }}"
commit_parser = '^(?P<change_type>feat|fix|refactor|test|perf|misc):\s(?P<message>.*)'
schema_pattern = '(feat|fix|refactor|test|perf|misc)(\((api|core)\))?:\s(.{3,})'
bump_pattern = "^(feat|fix|refactor|test|perf|misc)"
change_type_map = { "feat" = "Новое", "fix" = "Исправление" }

[[tool.commitizen.customize.questions]]
name = "change_type"
type = "list"
message = "Выберите тип изменений"
choices = [
{ value = "feat", name = "feat: Новая функциональность" },
{ value = "fix", name = "fix: Исправление" },
{ value = "refactor", name = "refactor: Рефакторинг" },
{ value = "test", name = "test: Изменение авто-тестов" },
{ value = "perf", name = "perf: Оптимизации" },
{ value = "misc", name = "misc: Другое" },
]
154 changes: 154 additions & 0 deletions tests/providers/test_base_provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,13 @@
from commitizen.exceptions import VersionProviderUnknown
from commitizen.providers import get_provider
from commitizen.providers.commitizen_provider import CommitizenProvider
from commitizen.providers.composer_provider import ComposerProvider
from commitizen.providers.pep621_provider import Pep621Provider
from commitizen.providers.uv_provider import UvProvider

if TYPE_CHECKING:
from pathlib import Path

from commitizen.config.base_config import BaseConfig


Expand All @@ -22,3 +27,152 @@ def test_raise_for_unknown_provider(config: BaseConfig):
config.settings["version_provider"] = "unknown"
with pytest.raises(VersionProviderUnknown):
get_provider(config)


@pytest.mark.parametrize("encoding", ["utf-8", "latin-1"])
def test_file_provider_get_encoding(config: BaseConfig, encoding: str):
"""_get_encoding should return the configured encoding."""
config.settings["encoding"] = encoding
provider = ComposerProvider(config)
assert provider._get_encoding() == encoding


def test_json_provider_uses_encoding_with_encoding_fixture(
config: BaseConfig,
chdir: Path,
data_dir: Path,
):
"""JsonProvider should correctly read a JSON file with non-ASCII content."""
source = data_dir / "encoding_test_composer.json"
target = chdir / "composer.json"
target.write_text(source.read_text(encoding="utf-8"), encoding="utf-8")

config.settings["encoding"] = "utf-8"
config.settings["version_provider"] = "composer"

provider = get_provider(config)
assert isinstance(provider, ComposerProvider)
assert provider.get_version() == "0.1.0"


def test_toml_provider_uses_encoding_with_encoding_fixture(
config: BaseConfig,
chdir: Path,
data_dir: Path,
):
"""TomlProvider should correctly read a TOML file with non-ASCII content."""
source = data_dir / "encoding_test_pyproject.toml"
target = chdir / "pyproject.toml"
target.write_text(source.read_text(encoding="utf-8"), encoding="utf-8")

config.settings["encoding"] = "utf-8"
config.settings["version_provider"] = "uv"

provider = get_provider(config)
assert isinstance(provider, UvProvider)
assert provider.get_version() == "0.4.1"


def test_json_provider_set_version_uses_encoding_with_encoding_fixture(
config: BaseConfig,
chdir: Path,
data_dir: Path,
):
"""JsonProvider.set_version should correctly write a JSON file with non-ASCII content."""
source = data_dir / "encoding_test_composer.json"
target = chdir / "composer.json"
target.write_text(source.read_text(encoding="utf-8"), encoding="utf-8")

config.settings["encoding"] = "utf-8"
config.settings["version_provider"] = "composer"

provider = get_provider(config)
assert isinstance(provider, ComposerProvider)
# Update version using set_version
provider.set_version("1.0.0")

# Verify the file can be read back with the same encoding
assert provider.get_version() == "1.0.0"

# Verify the non-ASCII content is preserved
content = target.read_text(encoding="utf-8")
assert "Тест описания для проверки кодировки" in content


def test_json_provider_handles_various_unicode_characters(
config: BaseConfig,
chdir: Path,
):
"""JsonProvider should handle a wide range of Unicode characters."""
config.settings["encoding"] = "utf-8"
config.settings["version_provider"] = "composer"

filename = ComposerProvider.filename
file = chdir / filename
file.write_text(
(
"{\n"
' "name": "多言語-имя-árbol",\n'
' "description": "Emoji 😀 – 漢字 – العربية",\n'
' "version": "0.1.0"\n'
"}\n"
),
encoding="utf-8",
)

provider = get_provider(config)
assert isinstance(provider, ComposerProvider)
assert provider.get_version() == "0.1.0"


def test_toml_provider_set_version_uses_encoding_with_encoding_fixture(
config: BaseConfig,
chdir: Path,
data_dir: Path,
):
"""TomlProvider.set_version should correctly write a TOML file with non-ASCII content."""
source = data_dir / "encoding_test_pyproject.toml"
target = chdir / "pyproject.toml"
target.write_text(source.read_text(encoding="utf-8"), encoding="utf-8")

config.settings["encoding"] = "utf-8"
config.settings["version_provider"] = "pep621"

provider = get_provider(config)
assert isinstance(provider, Pep621Provider)
# Update version using set_version
provider.set_version("1.0.0")

# Verify the file can be read back with the same encoding
assert provider.get_version() == "1.0.0"

# Verify the non-ASCII content is preserved
content = target.read_text(encoding="utf-8")
assert "Новое" in content
assert "Исправление" in content
assert "Выберите тип изменений" in content


def test_toml_provider_handles_various_unicode_characters(
config: BaseConfig,
chdir: Path,
):
"""TomlProvider should handle a wide range of Unicode characters."""
config.settings["encoding"] = "utf-8"
config.settings["version_provider"] = "pep621"

filename = Pep621Provider.filename
file = chdir / filename
file.write_text(
(
"[project]\n"
'name = "多言語-имя-árbol"\n'
'description = "Emoji 😀 – 漢字 – العربية"\n'
'version = "0.1.0"\n'
),
encoding="utf-8",
)

provider = get_provider(config)
assert isinstance(provider, Pep621Provider)
assert provider.get_version() == "0.1.0"