multicz 0.3.0__tar.gz → 0.4.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {multicz-0.3.0 → multicz-0.4.0}/PKG-INFO +23 -1
- {multicz-0.3.0 → multicz-0.4.0}/README.md +22 -0
- {multicz-0.3.0 → multicz-0.4.0}/pyproject.toml +1 -1
- {multicz-0.3.0 → multicz-0.4.0}/src/multicz/writers.py +56 -0
- {multicz-0.3.0 → multicz-0.4.0}/src/multicz/__init__.py +0 -0
- {multicz-0.3.0 → multicz-0.4.0}/src/multicz/__main__.py +0 -0
- {multicz-0.3.0 → multicz-0.4.0}/src/multicz/changelog.py +0 -0
- {multicz-0.3.0 → multicz-0.4.0}/src/multicz/cli.py +0 -0
- {multicz-0.3.0 → multicz-0.4.0}/src/multicz/commits.py +0 -0
- {multicz-0.3.0 → multicz-0.4.0}/src/multicz/components.py +0 -0
- {multicz-0.3.0 → multicz-0.4.0}/src/multicz/config.py +0 -0
- {multicz-0.3.0 → multicz-0.4.0}/src/multicz/debian.py +0 -0
- {multicz-0.3.0 → multicz-0.4.0}/src/multicz/discovery.py +0 -0
- {multicz-0.3.0 → multicz-0.4.0}/src/multicz/planner.py +0 -0
- {multicz-0.3.0 → multicz-0.4.0}/src/multicz/state.py +0 -0
- {multicz-0.3.0 → multicz-0.4.0}/src/multicz/validation.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: multicz
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Summary: Multi-component versioning for monorepos: bump apps, charts, and images independently from conventional commits.
|
|
5
5
|
Keywords: semver,monorepo,helm,conventional-commits,release,versioning
|
|
6
6
|
Author: Chris
|
|
@@ -960,6 +960,28 @@ exec multicz check "$1"
|
|
|
960
960
|
* `.properties` — line-based `key=value` substitution (e.g. `gradle.properties`)
|
|
961
961
|
* anything else — treated as a one-line `VERSION` file (`key = ` omitted)
|
|
962
962
|
|
|
963
|
+
### Regex escape hatch (`key = "regex:..."`)
|
|
964
|
+
|
|
965
|
+
For files no structured parser handles — Python `__version__`,
|
|
966
|
+
TypeScript `export const VERSION`, Cargo `version = "..."` *outside*
|
|
967
|
+
the `[package]` table, Makefile `VERSION := ...`, shell scripts,
|
|
968
|
+
Dockerfile `LABEL version=...` — prefix the key with `regex:`:
|
|
969
|
+
|
|
970
|
+
```toml
|
|
971
|
+
[components.api]
|
|
972
|
+
bump_files = [
|
|
973
|
+
{ file = "pyproject.toml", key = "project.version" },
|
|
974
|
+
{ file = "src/api/__init__.py", key = 'regex:^__version__\s*=\s*"([^"]+)"' },
|
|
975
|
+
]
|
|
976
|
+
```
|
|
977
|
+
|
|
978
|
+
The pattern needs exactly one capture group locating the version
|
|
979
|
+
literal. Matching uses `re.MULTILINE`, and only the *first* match's
|
|
980
|
+
capture group is rewritten — surrounding text (quotes, indentation,
|
|
981
|
+
comments, the rest of the file) is preserved byte-for-byte. Bad
|
|
982
|
+
patterns (uncompilable, no group, no match in file) surface at
|
|
983
|
+
`multicz validate --strict` rather than at bump time.
|
|
984
|
+
|
|
963
985
|
### Debian packages (`format = "debian"`)
|
|
964
986
|
|
|
965
987
|
`multicz` writes a proper `debian/changelog` instead of a markdown
|
|
@@ -932,6 +932,28 @@ exec multicz check "$1"
|
|
|
932
932
|
* `.properties` — line-based `key=value` substitution (e.g. `gradle.properties`)
|
|
933
933
|
* anything else — treated as a one-line `VERSION` file (`key = ` omitted)
|
|
934
934
|
|
|
935
|
+
### Regex escape hatch (`key = "regex:..."`)
|
|
936
|
+
|
|
937
|
+
For files no structured parser handles — Python `__version__`,
|
|
938
|
+
TypeScript `export const VERSION`, Cargo `version = "..."` *outside*
|
|
939
|
+
the `[package]` table, Makefile `VERSION := ...`, shell scripts,
|
|
940
|
+
Dockerfile `LABEL version=...` — prefix the key with `regex:`:
|
|
941
|
+
|
|
942
|
+
```toml
|
|
943
|
+
[components.api]
|
|
944
|
+
bump_files = [
|
|
945
|
+
{ file = "pyproject.toml", key = "project.version" },
|
|
946
|
+
{ file = "src/api/__init__.py", key = 'regex:^__version__\s*=\s*"([^"]+)"' },
|
|
947
|
+
]
|
|
948
|
+
```
|
|
949
|
+
|
|
950
|
+
The pattern needs exactly one capture group locating the version
|
|
951
|
+
literal. Matching uses `re.MULTILINE`, and only the *first* match's
|
|
952
|
+
capture group is rewritten — surrounding text (quotes, indentation,
|
|
953
|
+
comments, the rest of the file) is preserved byte-for-byte. Bad
|
|
954
|
+
patterns (uncompilable, no group, no match in file) surface at
|
|
955
|
+
`multicz validate --strict` rather than at bump time.
|
|
956
|
+
|
|
935
957
|
### Debian packages (`format = "debian"`)
|
|
936
958
|
|
|
937
959
|
`multicz` writes a proper `debian/changelog` instead of a markdown
|
|
@@ -13,22 +13,54 @@ A ``key`` is a dotted path (``project.version``, ``image.tag``). Passing
|
|
|
13
13
|
files the dotted-path interpretation is disabled — the key is taken
|
|
14
14
|
verbatim, since properties files routinely use dotted keys (``a.b.c``)
|
|
15
15
|
that are *not* nested.
|
|
16
|
+
|
|
17
|
+
A ``key`` prefixed with ``regex:`` is a language-agnostic escape hatch:
|
|
18
|
+
the rest of the string is a regex with one capture group locating the
|
|
19
|
+
version literal. Useful for ``__version__ = "X"`` in Python,
|
|
20
|
+
``export const VERSION = "X"`` in TypeScript, ``VERSION := X`` in
|
|
21
|
+
Makefiles, etc. The regex is anchored with :data:`re.MULTILINE`, and
|
|
22
|
+
only the *first* match's capture group is rewritten.
|
|
16
23
|
"""
|
|
17
24
|
|
|
18
25
|
from __future__ import annotations
|
|
19
26
|
|
|
20
27
|
import io
|
|
21
28
|
import json
|
|
29
|
+
import re
|
|
22
30
|
from pathlib import Path
|
|
23
31
|
|
|
24
32
|
import tomlkit
|
|
25
33
|
from ruamel.yaml import YAML
|
|
26
34
|
|
|
35
|
+
REGEX_KEY_PREFIX = "regex:"
|
|
36
|
+
|
|
27
37
|
|
|
28
38
|
class WriterError(RuntimeError):
|
|
29
39
|
"""Raised when a value cannot be read or written."""
|
|
30
40
|
|
|
31
41
|
|
|
42
|
+
def _is_regex_key(key: str | None) -> bool:
|
|
43
|
+
return key is not None and key.startswith(REGEX_KEY_PREFIX)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _compile_regex_key(key: str) -> re.Pattern[str]:
|
|
47
|
+
pattern = key[len(REGEX_KEY_PREFIX):]
|
|
48
|
+
if not pattern:
|
|
49
|
+
raise WriterError(f"empty regex pattern in key: {key!r}")
|
|
50
|
+
try:
|
|
51
|
+
compiled = re.compile(pattern, re.MULTILINE)
|
|
52
|
+
except re.error as exc:
|
|
53
|
+
raise WriterError(
|
|
54
|
+
f"invalid regex {pattern!r} in key {key!r}: {exc}"
|
|
55
|
+
) from exc
|
|
56
|
+
if compiled.groups < 1:
|
|
57
|
+
raise WriterError(
|
|
58
|
+
f"regex {pattern!r} must contain exactly one capture "
|
|
59
|
+
"group locating the version literal"
|
|
60
|
+
)
|
|
61
|
+
return compiled
|
|
62
|
+
|
|
63
|
+
|
|
32
64
|
def _split_key(key: str) -> list[str]:
|
|
33
65
|
parts = [part for part in key.split(".") if part]
|
|
34
66
|
if not parts:
|
|
@@ -121,6 +153,14 @@ def read_value(file: Path, key: str | None) -> str:
|
|
|
121
153
|
text = file.read_text(encoding="utf-8")
|
|
122
154
|
if key is None:
|
|
123
155
|
return text.strip()
|
|
156
|
+
if _is_regex_key(key):
|
|
157
|
+
pattern = _compile_regex_key(key)
|
|
158
|
+
match = pattern.search(text)
|
|
159
|
+
if not match:
|
|
160
|
+
raise WriterError(
|
|
161
|
+
f"regex {key!r} matched nothing in {file}"
|
|
162
|
+
)
|
|
163
|
+
return match.group(1)
|
|
124
164
|
if _is_properties(file):
|
|
125
165
|
result = _read_property(text, key)
|
|
126
166
|
if result is None:
|
|
@@ -157,6 +197,22 @@ def write_value(file: Path, key: str | None, value: str) -> None:
|
|
|
157
197
|
file.write_text(value + "\n", encoding="utf-8")
|
|
158
198
|
return
|
|
159
199
|
|
|
200
|
+
if _is_regex_key(key):
|
|
201
|
+
pattern = _compile_regex_key(key)
|
|
202
|
+
text = file.read_text(encoding="utf-8")
|
|
203
|
+
match = pattern.search(text)
|
|
204
|
+
if not match:
|
|
205
|
+
raise WriterError(
|
|
206
|
+
f"regex {key!r} matched nothing in {file}"
|
|
207
|
+
)
|
|
208
|
+
# Replace only the first match's capture group, preserving the
|
|
209
|
+
# surrounding text byte-for-byte (quotes, indentation, comments).
|
|
210
|
+
g_start, g_end = match.span(1)
|
|
211
|
+
file.write_text(
|
|
212
|
+
text[:g_start] + value + text[g_end:], encoding="utf-8"
|
|
213
|
+
)
|
|
214
|
+
return
|
|
215
|
+
|
|
160
216
|
if _is_properties(file):
|
|
161
217
|
text = file.read_text(encoding="utf-8")
|
|
162
218
|
file.write_text(_write_property(text, key, value), encoding="utf-8")
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|