Skip to content

Commit

Permalink
#155: Add initial-content-jinja option
Browse files Browse the repository at this point in the history
  • Loading branch information
mtkennerly committed Dec 2, 2023
1 parent f0db558 commit 5c3c54a
Show file tree
Hide file tree
Showing 5 changed files with 84 additions and 44 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ __pycache__/
.idea/
.mypy_cache/
.pytest_cache/
.ruff_cache/
.tox/
.vagrant/
.venv/
Expand Down
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
## Unreleased

* Added:
* `initial-content-jinja` option in `tool.poetry-dynamic-versioning.files` section.
* Fixed:
* Line ending style was not preserved in some cases because of the default behavior of `pathlib.Path.read_text`.
To avoid this, `pathlib.Path.read_bytes` is used instead now.
Expand Down
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,13 @@ In your pyproject.toml file, you may configure the following options:
Set the file content before the substitution phase.
The file will be created or overwritten as necessary.
Common leading whitespace will be stripped from each line.
* `initial-content-jinja` (string, optional):
Same as `initial-content`, but using Jinja formatting.
If both options are set, this one takes priority.
You can use the same imports from `format-jinja-imports` and the same variables from `format-jinja`,
with this additional variable:

* `formatted_version` (string) - version formatted by either the `format` or `format-jinja` option

Example:

Expand Down
110 changes: 70 additions & 40 deletions poetry_dynamic_versioning/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,12 @@
)

_File = TypedDict(
"_File", {"persistent-substitution": Optional[bool], "initial-content": Optional[str]}
"_File",
{
"persistent-substitution": Optional[bool],
"initial-content": Optional[str],
"initial-content-jinja": Optional[str],
},
)

_JinjaImport = TypedDict(
Expand Down Expand Up @@ -254,13 +259,17 @@ def initialize(data, key):
if isinstance(data, dict) and key not in data:
data[key] = None

if isinstance(local, tomlkit.TOMLDocument):
local = local.unwrap()

merged = _deep_merge_dicts(_default_config(), local)["tool"][
"poetry-dynamic-versioning"
] # type: _Config

# Add default values so we don't have to worry about missing keys
for x in merged["files"].values():
initialize(x, "initial-content")
initialize(x, "initial-content-jinja")
initialize(x, "persistent-substitution")
for x in merged["format-jinja-imports"]:
initialize(x, "item")
Expand Down Expand Up @@ -326,6 +335,45 @@ def _format_timestamp(value: Optional[dt.datetime]) -> Optional[str]:
return value.strftime("%Y%m%d%H%M%S")


def _render_jinja(
version: Version, template: str, config: _Config, extra: Optional[Mapping] = None
) -> str:
if extra is None:
extra = {}

if config["bump"] and version.distance > 0:
version = version.bump()
default_context = {
"base": version.base,
"version": version,
"stage": version.stage,
"revision": version.revision,
"distance": version.distance,
"commit": version.commit,
"dirty": version.dirty,
"branch": version.branch,
"branch_escaped": _escape_branch(version.branch),
"timestamp": _format_timestamp(version.timestamp),
"env": os.environ,
"bump_version": bump_version,
"tagged_metadata": version.tagged_metadata,
"serialize_pep440": serialize_pep440,
"serialize_pvp": serialize_pvp,
"serialize_semver": serialize_semver,
**extra,
}
custom_context = {} # type: dict
for entry in config["format-jinja-imports"]:
if "module" in entry:
module = import_module(entry["module"])
if entry["item"] is not None:
custom_context[entry["item"]] = getattr(module, entry["item"])
else:
custom_context[entry["module"]] = module
serialized = jinja2.Template(template).render(**default_context, **custom_context)
return serialized


def _run_cmd(command: str, codes: Sequence[int] = (0,)) -> Tuple[int, str]:
result = subprocess.run(
shlex.split(command),
Expand Down Expand Up @@ -365,7 +413,7 @@ def _get_override_version(name: Optional[str], env: Optional[Mapping] = None) ->

def _get_version_from_dunamai(
vcs: Vcs, pattern: Union[str, Pattern], config: _Config, *, strict: Optional[bool] = None
):
) -> Version:
return Version.from_vcs(
vcs,
pattern,
Expand All @@ -377,10 +425,10 @@ def _get_version_from_dunamai(
)


def _get_version(config: _Config, name: Optional[str] = None) -> str:
def _get_version(config: _Config, name: Optional[str] = None) -> Tuple[str, Version]:
override = _get_override_version(name)
if override is not None:
return override
return (override, Version.parse(override))

vcs = Vcs(config["vcs"])
style = Style(config["style"]) if config["style"] is not None else None
Expand All @@ -407,37 +455,7 @@ def _get_version(config: _Config, name: Optional[str] = None) -> str:
print("Warning: {}".format(concern.message()), file=sys.stderr)

if config["format-jinja"]:
if config["bump"] and version.distance > 0:
version = version.bump()
default_context = {
"base": version.base,
"version": version,
"stage": version.stage,
"revision": version.revision,
"distance": version.distance,
"commit": version.commit,
"dirty": version.dirty,
"branch": version.branch,
"branch_escaped": _escape_branch(version.branch),
"timestamp": _format_timestamp(version.timestamp),
"env": os.environ,
"bump_version": bump_version,
"tagged_metadata": version.tagged_metadata,
"serialize_pep440": serialize_pep440,
"serialize_pvp": serialize_pvp,
"serialize_semver": serialize_semver,
}
custom_context = {} # type: dict
for entry in config["format-jinja-imports"]:
if "module" in entry:
module = import_module(entry["module"])
if entry["item"] is not None:
custom_context[entry["item"]] = getattr(module, entry["item"])
else:
custom_context[entry["module"]] = module
serialized = jinja2.Template(config["format-jinja"]).render(
**default_context, **custom_context
)
serialized = _render_jinja(version, config["format-jinja"], config)
if style is not None:
check_version(serialized, style)
else:
Expand All @@ -450,7 +468,7 @@ def _get_version(config: _Config, name: Optional[str] = None) -> str:
tagged_metadata=config["tagged-metadata"],
)

return serialized
return (serialized, version)


def _substitute_version(name: str, version: str, folders: Sequence[_FolderConfig]) -> None:
Expand Down Expand Up @@ -507,7 +525,7 @@ def _substitute_version_in_text(version: str, content: str, patterns: Sequence[_


def _apply_version(
version: str, config: _Config, pyproject_path: Path, retain: bool = False
version: str, instance: Version, config: _Config, pyproject_path: Path, retain: bool = False
) -> None:
pyproject = tomlkit.parse(pyproject_path.read_bytes().decode("utf-8"))

Expand All @@ -526,7 +544,19 @@ def _apply_version(
for file_name, file_info in config["files"].items():
full_file = pyproject_path.parent.joinpath(file_name)

if file_info["initial-content"] is not None:
if file_info["initial-content-jinja"] is not None:
if not full_file.parent.exists():
full_file.parent.mkdir()
initial = textwrap.dedent(
_render_jinja(
instance,
file_info["initial-content-jinja"],
config,
{"formatted_version": version},
)
)
full_file.write_bytes(initial.encode("utf-8"))
elif file_info["initial-content"] is not None:
if not full_file.parent.exists():
full_file.parent.mkdir()
initial = textwrap.dedent(file_info["initial-content"])
Expand Down Expand Up @@ -575,15 +605,15 @@ def _get_and_apply_version(
target_dir = pyproject_path.parent
os.chdir(str(target_dir))
try:
version = _get_version(config, name)
version, instance = _get_version(config, name)
finally:
os.chdir(str(initial_dir))

# Condition will always be true, but it makes Mypy happy.
if name is not None and original is not None:
_state.projects[name] = _ProjectState(pyproject_path, original, version)
if io:
_apply_version(version, config, pyproject_path, retain)
_apply_version(version, instance, config, pyproject_path, retain)

return name

Expand Down
8 changes: 4 additions & 4 deletions tests/test_unit.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ def test__get_config_from_path__with_plugin_customizations():


def test__get_version__defaults(config):
assert plugin._get_version(config) == Version.from_git().serialize()
assert plugin._get_version(config)[0] == Version.from_git().serialize()


def test__get_version__invalid_vcs(config):
Expand All @@ -68,7 +68,7 @@ def test__get_version__invalid_style(config):
def test__get_version__format_jinja(config):
os.environ["FOO"] = "foo"
config["format-jinja"] = "{% if true %}v1+{{ env['FOO'] }}{% endif %}"
assert plugin._get_version(config) == "v1+foo"
assert plugin._get_version(config)[0] == "v1+foo"


def test__get_version__format_jinja_with_enforced_style(config):
Expand All @@ -81,13 +81,13 @@ def test__get_version__format_jinja_with_enforced_style(config):
def test__get_version__format_jinja_imports_with_module_only(config):
config["format-jinja"] = "{{ math.pow(2, 2) }}"
config["format-jinja-imports"] = [{"module": "math", "item": None}]
assert plugin._get_version(config) == "4.0"
assert plugin._get_version(config)[0] == "4.0"


def test__get_version__format_jinja_imports_with_module_and_item(config):
config["format-jinja"] = "{{ pow(2, 3) }}"
config["format-jinja-imports"] = [{"module": "math", "item": "pow"}]
assert plugin._get_version(config) == "8.0"
assert plugin._get_version(config)[0] == "8.0"


def test__get_override_version__bypass():
Expand Down

0 comments on commit 5c3c54a

Please sign in to comment.