esys-gitlog 0.1.0__tar.gz → 0.2.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.
Files changed (43) hide show
  1. {esys_gitlog-0.1.0/src/esys_gitlog.egg-info → esys_gitlog-0.2.0}/PKG-INFO +9 -9
  2. {esys_gitlog-0.1.0 → esys_gitlog-0.2.0}/README.md +8 -8
  3. {esys_gitlog-0.1.0 → esys_gitlog-0.2.0}/pyproject.toml +2 -1
  4. {esys_gitlog-0.1.0 → esys_gitlog-0.2.0}/src/esys_gitlog/__init__.py +1 -1
  5. {esys_gitlog-0.1.0 → esys_gitlog-0.2.0}/src/esys_gitlog/cli.py +6 -2
  6. {esys_gitlog-0.1.0 → esys_gitlog-0.2.0}/src/esys_gitlog/config.py +12 -5
  7. {esys_gitlog-0.1.0 → esys_gitlog-0.2.0}/src/esys_gitlog/display.py +21 -11
  8. {esys_gitlog-0.1.0 → esys_gitlog-0.2.0}/src/esys_gitlog/identifiers.py +30 -0
  9. {esys_gitlog-0.1.0 → esys_gitlog-0.2.0}/src/esys_gitlog/interpret.py +28 -4
  10. {esys_gitlog-0.1.0 → esys_gitlog-0.2.0}/src/esys_gitlog/preferences.py +1 -1
  11. {esys_gitlog-0.1.0 → esys_gitlog-0.2.0/src/esys_gitlog.egg-info}/PKG-INFO +9 -9
  12. {esys_gitlog-0.1.0 → esys_gitlog-0.2.0}/src/esys_gitlog.egg-info/entry_points.txt +1 -0
  13. {esys_gitlog-0.1.0 → esys_gitlog-0.2.0}/tests/test_browser_display.py +2 -1
  14. {esys_gitlog-0.1.0 → esys_gitlog-0.2.0}/tests/test_installed_launcher.py +31 -12
  15. {esys_gitlog-0.1.0 → esys_gitlog-0.2.0}/tests/test_semantic_p4.py +26 -0
  16. {esys_gitlog-0.1.0 → esys_gitlog-0.2.0}/LICENSE.txt +0 -0
  17. {esys_gitlog-0.1.0 → esys_gitlog-0.2.0}/setup.cfg +0 -0
  18. {esys_gitlog-0.1.0 → esys_gitlog-0.2.0}/src/esys_gitlog/__main__.py +0 -0
  19. {esys_gitlog-0.1.0 → esys_gitlog-0.2.0}/src/esys_gitlog/app.py +0 -0
  20. {esys_gitlog-0.1.0 → esys_gitlog-0.2.0}/src/esys_gitlog/detection.py +0 -0
  21. {esys_gitlog-0.1.0 → esys_gitlog-0.2.0}/src/esys_gitlog/git_adapter.py +0 -0
  22. {esys_gitlog-0.1.0 → esys_gitlog-0.2.0}/src/esys_gitlog/links.py +0 -0
  23. {esys_gitlog-0.1.0 → esys_gitlog-0.2.0}/src/esys_gitlog/model.py +0 -0
  24. {esys_gitlog-0.1.0 → esys_gitlog-0.2.0}/src/esys_gitlog/pager.py +0 -0
  25. {esys_gitlog-0.1.0 → esys_gitlog-0.2.0}/src/esys_gitlog/pipeline.py +0 -0
  26. {esys_gitlog-0.1.0 → esys_gitlog-0.2.0}/src/esys_gitlog/qa.py +0 -0
  27. {esys_gitlog-0.1.0 → esys_gitlog-0.2.0}/src/esys_gitlog/render.py +0 -0
  28. {esys_gitlog-0.1.0 → esys_gitlog-0.2.0}/src/esys_gitlog/report.py +0 -0
  29. {esys_gitlog-0.1.0 → esys_gitlog-0.2.0}/src/esys_gitlog/tool_paths.py +0 -0
  30. {esys_gitlog-0.1.0 → esys_gitlog-0.2.0}/src/esys_gitlog.egg-info/SOURCES.txt +0 -0
  31. {esys_gitlog-0.1.0 → esys_gitlog-0.2.0}/src/esys_gitlog.egg-info/dependency_links.txt +0 -0
  32. {esys_gitlog-0.1.0 → esys_gitlog-0.2.0}/src/esys_gitlog.egg-info/requires.txt +0 -0
  33. {esys_gitlog-0.1.0 → esys_gitlog-0.2.0}/src/esys_gitlog.egg-info/top_level.txt +0 -0
  34. {esys_gitlog-0.1.0 → esys_gitlog-0.2.0}/tests/test_cli.py +0 -0
  35. {esys_gitlog-0.1.0 → esys_gitlog-0.2.0}/tests/test_coverage_gaps.py +0 -0
  36. {esys_gitlog-0.1.0 → esys_gitlog-0.2.0}/tests/test_detection.py +0 -0
  37. {esys_gitlog-0.1.0 → esys_gitlog-0.2.0}/tests/test_git_adapter.py +0 -0
  38. {esys_gitlog-0.1.0 → esys_gitlog-0.2.0}/tests/test_pager.py +0 -0
  39. {esys_gitlog-0.1.0 → esys_gitlog-0.2.0}/tests/test_preferences.py +0 -0
  40. {esys_gitlog-0.1.0 → esys_gitlog-0.2.0}/tests/test_render.py +0 -0
  41. {esys_gitlog-0.1.0 → esys_gitlog-0.2.0}/tests/test_semantic_default.py +0 -0
  42. {esys_gitlog-0.1.0 → esys_gitlog-0.2.0}/tests/test_smoke.py +0 -0
  43. {esys_gitlog-0.1.0 → esys_gitlog-0.2.0}/tests/test_tool_paths.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: esys-gitlog
3
- Version: 0.1.0
3
+ Version: 0.2.0
4
4
  Summary: Python implementation of git elog
5
5
  Author: Project maintainers
6
6
  License-Expression: MIT
@@ -22,7 +22,7 @@ Dynamic: license-file
22
22
 
23
23
  # esys-gitlog
24
24
 
25
- Python implementation of `git elog`. The distribution name is `esys-gitlog`. The import package is `esys_gitlog`. The installed command is `git-elog`, so Git can run `git elog`.
25
+ Python implementation of `git elog`. The distribution name is `esys-gitlog`. The import package is `esys_gitlog`. Installed commands: `git-elog` (so Git can run `git elog`) and the shorter alias `elog`. Prefer `elog --help` — `git elog --help` is handled by Git's own help path, not the Typer CLI.
26
26
 
27
27
  The shared contract lives in `esys-gitlog-spec`. This repository carries that contract in Python. It does not define a second one.
28
28
 
@@ -30,8 +30,8 @@ The shared contract lives in `esys-gitlog-spec`. This repository carries that co
30
30
 
31
31
  ```bash
32
32
  pip install .
33
- git elog --help
34
- git elog --json
33
+ elog --help
34
+ elog --json
35
35
  ```
36
36
 
37
37
  `--json` writes one QA report document to stdout. The first skeleton returns an empty `commits` array.
@@ -52,15 +52,15 @@ Layered tool preferences (user → `.tools/esys/esys-gitlog/` → `.tools_local/
52
52
  and named profiles follow root SADR-0009 / SADR-0010 and contract CADR-0012.
53
53
 
54
54
  ```bash
55
- git-elog --preferences path.json # preference overlay
56
- git-elog --profile acme # built-in or profiles/acme.json
57
- git-elog --config path/profile.json # one-off profile file
58
- git-elog preferences detect # probe jira/gh/glab/git remote; write missing user keys
55
+ elog --preferences path.json # preference overlay
56
+ elog --profile acme # built-in or profiles/acme.json
57
+ elog --config path/profile.json # one-off profile file
58
+ elog preferences detect # probe jira/gh/glab/git remote; write missing user keys
59
59
  ```
60
60
 
61
61
  ## Standalone binary (optional)
62
62
 
63
- The primary install remains the wheel + `git-elog` console script. For a
63
+ The primary install remains the wheel + `git-elog` / `elog` console scripts. For a
64
64
  frozen one-file binary (for example Cursor `esysGitlog.executablePath` without
65
65
  a Python env on PATH):
66
66
 
@@ -1,6 +1,6 @@
1
1
  # esys-gitlog
2
2
 
3
- Python implementation of `git elog`. The distribution name is `esys-gitlog`. The import package is `esys_gitlog`. The installed command is `git-elog`, so Git can run `git elog`.
3
+ Python implementation of `git elog`. The distribution name is `esys-gitlog`. The import package is `esys_gitlog`. Installed commands: `git-elog` (so Git can run `git elog`) and the shorter alias `elog`. Prefer `elog --help` — `git elog --help` is handled by Git's own help path, not the Typer CLI.
4
4
 
5
5
  The shared contract lives in `esys-gitlog-spec`. This repository carries that contract in Python. It does not define a second one.
6
6
 
@@ -8,8 +8,8 @@ The shared contract lives in `esys-gitlog-spec`. This repository carries that co
8
8
 
9
9
  ```bash
10
10
  pip install .
11
- git elog --help
12
- git elog --json
11
+ elog --help
12
+ elog --json
13
13
  ```
14
14
 
15
15
  `--json` writes one QA report document to stdout. The first skeleton returns an empty `commits` array.
@@ -30,15 +30,15 @@ Layered tool preferences (user → `.tools/esys/esys-gitlog/` → `.tools_local/
30
30
  and named profiles follow root SADR-0009 / SADR-0010 and contract CADR-0012.
31
31
 
32
32
  ```bash
33
- git-elog --preferences path.json # preference overlay
34
- git-elog --profile acme # built-in or profiles/acme.json
35
- git-elog --config path/profile.json # one-off profile file
36
- git-elog preferences detect # probe jira/gh/glab/git remote; write missing user keys
33
+ elog --preferences path.json # preference overlay
34
+ elog --profile acme # built-in or profiles/acme.json
35
+ elog --config path/profile.json # one-off profile file
36
+ elog preferences detect # probe jira/gh/glab/git remote; write missing user keys
37
37
  ```
38
38
 
39
39
  ## Standalone binary (optional)
40
40
 
41
- The primary install remains the wheel + `git-elog` console script. For a
41
+ The primary install remains the wheel + `git-elog` / `elog` console scripts. For a
42
42
  frozen one-file binary (for example Cursor `esysGitlog.executablePath` without
43
43
  a Python env on PATH):
44
44
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "esys-gitlog"
7
- version = "0.1.0"
7
+ version = "0.2.0"
8
8
  description = "Python implementation of git elog"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -30,6 +30,7 @@ standalone = [
30
30
 
31
31
  [project.scripts]
32
32
  git-elog = "esys_gitlog.cli:main"
33
+ elog = "esys_gitlog.cli:main"
33
34
 
34
35
  [tool.setuptools]
35
36
  package-dir = { "" = "src" }
@@ -1,3 +1,3 @@
1
1
  """esys_gitlog — installable package."""
2
2
 
3
- __version__ = "0.1.0"
3
+ __version__ = "0.2.0"
@@ -1,4 +1,4 @@
1
- """Thin Typer command boundary for ``git-elog`` / ``git elog``."""
1
+ """Thin Typer command boundary for ``git-elog`` / ``elog`` / ``git elog``."""
2
2
 
3
3
  from __future__ import annotations
4
4
 
@@ -316,7 +316,11 @@ def preferences_detect(
316
316
 
317
317
  def main() -> None:
318
318
  """Console script entry point."""
319
- app(args=prepare_argv(sys.argv[1:]), prog_name="git-elog")
319
+ invoked = Path(sys.argv[0]).name
320
+ if invoked.lower().endswith(".exe"):
321
+ invoked = invoked[:-4]
322
+ prog_name = invoked or "git-elog"
323
+ app(args=prepare_argv(sys.argv[1:]), prog_name=prog_name)
320
324
 
321
325
 
322
326
  if __name__ == "__main__":
@@ -49,9 +49,9 @@ class ProfileRules:
49
49
 
50
50
  name: str
51
51
  conventional: bool = True
52
- conventional_locations: tuple[str, ...] = ("title",)
52
+ conventional_locations: tuple[str, ...] = ("title", "trailer")
53
53
  references: bool = True
54
- reference_locations: tuple[str, ...] = ("refs",)
54
+ reference_locations: tuple[str, ...] = ("prefix", "suffix", "refs")
55
55
  identifier_family: str = "jira-id"
56
56
  signed_off_by: bool = True
57
57
  types: tuple[str, ...] = DEFAULT_TYPES
@@ -70,7 +70,7 @@ class AnalysisConfig:
70
70
  display: DisplayMode = "subject-precedence"
71
71
  preferences: Preferences = field(default_factory=empty_preferences)
72
72
  # Human history: fixed-width refs / minimum Type(Scope) columns.
73
- ref_id_width: int = 10
73
+ ref_id_width: int = 14
74
74
  type_width: int = 10
75
75
  show_author: bool = False
76
76
  show_date: bool = False
@@ -87,6 +87,7 @@ def _trailer_profile() -> ProfileRules:
87
87
  return ProfileRules(
88
88
  name="trailer",
89
89
  conventional_locations=("trailer",),
90
+ reference_locations=("refs",),
90
91
  )
91
92
 
92
93
 
@@ -97,8 +98,14 @@ KNOWN_PROFILES: dict[str, ProfileRules] = {
97
98
 
98
99
 
99
100
  def _profile_from_object(data: dict[str, Any], *, name: str = "custom") -> ProfileRules:
100
- conventional_locations = data.get("conventionalLocations", ["title"])
101
- reference_locations = data.get("referenceLocations", ["refs"])
101
+ conventional_locations = data.get(
102
+ "conventionalLocations",
103
+ ["title", "trailer"],
104
+ )
105
+ reference_locations = data.get(
106
+ "referenceLocations",
107
+ ["prefix", "suffix", "refs"],
108
+ )
102
109
  types = data.get("types", list(DEFAULT_TYPES))
103
110
  reference_keys = data.get("referenceKeys", ["Refs"])
104
111
  # Legacy profile link keys are ignored (CADR-0012); prefer preference files.
@@ -2,17 +2,16 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
- import re
6
-
7
5
  from esys_gitlog.config import AnalysisConfig
8
- from esys_gitlog.identifiers import matches_family, ref_identity_key
6
+ from esys_gitlog.identifiers import (
7
+ matches_family,
8
+ ref_identity_key,
9
+ strip_subject_edge_refs,
10
+ )
9
11
  from esys_gitlog.interpret import title_pattern
10
12
  from esys_gitlog.links import resolve_ref_url
11
13
  from esys_gitlog.model import CommitResult, DisplayRef, DisplaySelection
12
14
 
13
- _SUFFIX_REF = re.compile(r"(?:\s+|\[)(?P<token>[A-Za-z][A-Za-z0-9._-]*-\d+)\]?\s*$")
14
- _PREFIX_REF = re.compile(r"^(?:\[)?(?P<token>[A-Za-z][A-Za-z0-9._-]*-\d+)(?:\])?(?::|\s+)\s*")
15
-
16
15
 
17
16
  def _pick_single(
18
17
  result: CommitResult,
@@ -38,14 +37,25 @@ def _pick_single(
38
37
  def _cleaned_title(subject: str, config: AnalysisConfig) -> str:
39
38
  line = subject.splitlines()[0] if subject else ""
40
39
  rules = config.profile
40
+ strip_prefix = rules.references and "prefix" in rules.reference_locations
41
+ strip_suffix = rules.references and "suffix" in rules.reference_locations
41
42
  if rules.conventional and "title" in rules.conventional_locations:
42
43
  match = title_pattern(rules.types).fullmatch(line)
43
44
  if match is not None:
44
- return match.group("description")
45
- if rules.references:
46
- stripped = _PREFIX_REF.sub("", line, count=1)
47
- stripped = _SUFFIX_REF.sub("", stripped)
48
- return stripped.strip() or line
45
+ description = match.group("description")
46
+ cleaned = strip_subject_edge_refs(
47
+ description,
48
+ strip_prefix=strip_prefix,
49
+ strip_suffix=strip_suffix,
50
+ )
51
+ return cleaned or description
52
+ if rules.references and (strip_prefix or strip_suffix):
53
+ stripped = strip_subject_edge_refs(
54
+ line,
55
+ strip_prefix=strip_prefix,
56
+ strip_suffix=strip_suffix,
57
+ )
58
+ return stripped or line
49
59
  return line
50
60
 
51
61
 
@@ -7,6 +7,11 @@ import re
7
7
  # Jira key and the same shape used by YouTrack / Linear.
8
8
  _JIRA_KEY = re.compile(r"^[A-Za-z][A-Za-z0-9_]*-\d+$")
9
9
 
10
+ # Subject-edge reference tokens (CADR-0005): bare id or optional square brackets.
11
+ # Prefix may use a trailing colon instead of whitespace.
12
+ _SUBJECT_PREFIX_REF = re.compile(r"^(?:\[)?(?P<token>[A-Za-z][A-Za-z0-9_]*-\d+)(?:\])?(?::|\s+)\s*")
13
+ _SUBJECT_SUFFIX_REF = re.compile(r"(?:\s+|\[)(?P<token>[A-Za-z][A-Za-z0-9_]*-\d+)\]?\s*$")
14
+
10
15
  # Shapes from references.md that any-id accepts beyond the Jira key.
11
16
  _ANY_EXTRA = (
12
17
  re.compile(r"^#\d+$"),
@@ -44,3 +49,28 @@ def matches_family(token: str, family: str) -> bool:
44
49
  def ref_identity_key(token: str) -> str:
45
50
  """Letter case does not make a different id."""
46
51
  return token.casefold()
52
+
53
+
54
+ def match_subject_prefix_ref(subject_line: str) -> re.Match[str] | None:
55
+ """Match a leading subject-edge reference, if any."""
56
+ return _SUBJECT_PREFIX_REF.match(subject_line)
57
+
58
+
59
+ def match_subject_suffix_ref(subject_line: str) -> re.Match[str] | None:
60
+ """Match a trailing subject-edge reference, if any."""
61
+ return _SUBJECT_SUFFIX_REF.search(subject_line)
62
+
63
+
64
+ def strip_subject_edge_refs(
65
+ subject_line: str,
66
+ *,
67
+ strip_prefix: bool,
68
+ strip_suffix: bool,
69
+ ) -> str:
70
+ """Remove enabled edge references from a subject (or description) line."""
71
+ text = subject_line
72
+ if strip_prefix:
73
+ text = _SUBJECT_PREFIX_REF.sub("", text, count=1)
74
+ if strip_suffix:
75
+ text = _SUBJECT_SUFFIX_REF.sub("", text)
76
+ return text.strip()
@@ -5,11 +5,14 @@ from __future__ import annotations
5
5
  import re
6
6
 
7
7
  from esys_gitlog.config import AnalysisConfig, ProfileRules
8
- from esys_gitlog.identifiers import matches_family
8
+ from esys_gitlog.identifiers import (
9
+ match_subject_prefix_ref,
10
+ match_subject_suffix_ref,
11
+ matches_family,
12
+ )
9
13
  from esys_gitlog.model import Claim, CommitResult
10
14
 
11
15
  _TRAILER_LINE = re.compile(r"^(?P<key>[A-Za-z0-9][A-Za-z0-9 -]*):\s?(?P<value>.*)$")
12
- _PREFIX_REF = re.compile(r"^(?P<token>[A-Za-z][A-Za-z0-9._-]*-\d+)\b")
13
16
  _BREAKING_KEYS = frozenset({"breaking change", "breaking"})
14
17
 
15
18
 
@@ -117,6 +120,8 @@ def interpret_commit(subject: str, body: str, config: AnalysisConfig) -> CommitR
117
120
 
118
121
  if rules.references and "prefix" in rules.reference_locations:
119
122
  _interpret_prefix_refs(subject_line, rules, result)
123
+ if rules.references and "suffix" in rules.reference_locations:
124
+ _interpret_suffix_refs(subject_line, rules, result)
120
125
 
121
126
  trailer_type_ok = False
122
127
  for key, value, line, column, empty in _parse_trailers(body_lines):
@@ -265,7 +270,7 @@ def _interpret_title(subject_line: str, rules: ProfileRules, result: CommitResul
265
270
 
266
271
 
267
272
  def _interpret_prefix_refs(subject_line: str, rules: ProfileRules, result: CommitResult) -> None:
268
- match = _PREFIX_REF.match(subject_line)
273
+ match = match_subject_prefix_ref(subject_line)
269
274
  if match is None:
270
275
  return
271
276
  token = match.group("token")
@@ -275,7 +280,7 @@ def _interpret_prefix_refs(subject_line: str, rules: ProfileRules, result: Commi
275
280
  value=token,
276
281
  source="subject",
277
282
  line=1,
278
- column=1,
283
+ column=match.start("token") + 1,
279
284
  key="prefix",
280
285
  )
281
286
  )
@@ -283,6 +288,25 @@ def _interpret_prefix_refs(subject_line: str, rules: ProfileRules, result: Commi
283
288
  result.references_matched = True
284
289
 
285
290
 
291
+ def _interpret_suffix_refs(subject_line: str, rules: ProfileRules, result: CommitResult) -> None:
292
+ match = match_subject_suffix_ref(subject_line)
293
+ if match is None:
294
+ return
295
+ token = match.group("token")
296
+ result.claims.append(
297
+ Claim(
298
+ field="refs",
299
+ value=token,
300
+ source="subject",
301
+ line=1,
302
+ column=match.start("token") + 1,
303
+ key="suffix",
304
+ )
305
+ )
306
+ if matches_family(token, rules.identifier_family):
307
+ result.references_matched = True
308
+
309
+
286
310
  def _interpret_refs(
287
311
  key: str,
288
312
  value: str,
@@ -22,7 +22,7 @@ class LinkPreferences:
22
22
  commit_link: str | None = None
23
23
 
24
24
 
25
- DEFAULT_REF_ID_WIDTH = 10
25
+ DEFAULT_REF_ID_WIDTH = 14
26
26
  DEFAULT_TYPE_WIDTH = 10
27
27
 
28
28
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: esys-gitlog
3
- Version: 0.1.0
3
+ Version: 0.2.0
4
4
  Summary: Python implementation of git elog
5
5
  Author: Project maintainers
6
6
  License-Expression: MIT
@@ -22,7 +22,7 @@ Dynamic: license-file
22
22
 
23
23
  # esys-gitlog
24
24
 
25
- Python implementation of `git elog`. The distribution name is `esys-gitlog`. The import package is `esys_gitlog`. The installed command is `git-elog`, so Git can run `git elog`.
25
+ Python implementation of `git elog`. The distribution name is `esys-gitlog`. The import package is `esys_gitlog`. Installed commands: `git-elog` (so Git can run `git elog`) and the shorter alias `elog`. Prefer `elog --help` — `git elog --help` is handled by Git's own help path, not the Typer CLI.
26
26
 
27
27
  The shared contract lives in `esys-gitlog-spec`. This repository carries that contract in Python. It does not define a second one.
28
28
 
@@ -30,8 +30,8 @@ The shared contract lives in `esys-gitlog-spec`. This repository carries that co
30
30
 
31
31
  ```bash
32
32
  pip install .
33
- git elog --help
34
- git elog --json
33
+ elog --help
34
+ elog --json
35
35
  ```
36
36
 
37
37
  `--json` writes one QA report document to stdout. The first skeleton returns an empty `commits` array.
@@ -52,15 +52,15 @@ Layered tool preferences (user → `.tools/esys/esys-gitlog/` → `.tools_local/
52
52
  and named profiles follow root SADR-0009 / SADR-0010 and contract CADR-0012.
53
53
 
54
54
  ```bash
55
- git-elog --preferences path.json # preference overlay
56
- git-elog --profile acme # built-in or profiles/acme.json
57
- git-elog --config path/profile.json # one-off profile file
58
- git-elog preferences detect # probe jira/gh/glab/git remote; write missing user keys
55
+ elog --preferences path.json # preference overlay
56
+ elog --profile acme # built-in or profiles/acme.json
57
+ elog --config path/profile.json # one-off profile file
58
+ elog preferences detect # probe jira/gh/glab/git remote; write missing user keys
59
59
  ```
60
60
 
61
61
  ## Standalone binary (optional)
62
62
 
63
- The primary install remains the wheel + `git-elog` console script. For a
63
+ The primary install remains the wheel + `git-elog` / `elog` console scripts. For a
64
64
  frozen one-file binary (for example Cursor `esysGitlog.executablePath` without
65
65
  a Python env on PATH):
66
66
 
@@ -1,2 +1,3 @@
1
1
  [console_scripts]
2
+ elog = esys_gitlog.cli:main
2
3
  git-elog = esys_gitlog.cli:main
@@ -13,7 +13,8 @@ def test_cleaned_title_strips_conventional_prefix() -> None:
13
13
  assert result.display.type == "feat"
14
14
  assert result.display.scope == "api"
15
15
  assert result.display.breaking is True
16
- assert result.display.title == "add login PROJ-1"
16
+ assert result.display.title == "add login"
17
+ assert [ref.id for ref in result.display.refs] == ["PROJ-1"]
17
18
 
18
19
 
19
20
  def test_display_refs_from_trailer() -> None:
@@ -19,10 +19,10 @@ def _venv_paths(venv: Path) -> tuple[Path, Path]:
19
19
  return venv / "bin" / "python", venv / "bin"
20
20
 
21
21
 
22
- def _git_elog_executable(scripts: Path) -> Path:
22
+ def _console_script(scripts: Path, name: str) -> Path:
23
23
  if sys.platform == "win32":
24
- return scripts / "git-elog.exe"
25
- return scripts / "git-elog"
24
+ return scripts / f"{name}.exe"
25
+ return scripts / name
26
26
 
27
27
 
28
28
  @pytest.fixture(scope="module")
@@ -45,8 +45,10 @@ def installed_env(
45
45
  check=True,
46
46
  cwd=elsewhere,
47
47
  )
48
- launcher = _git_elog_executable(scripts)
49
- assert launcher.is_file(), f"missing installed launcher: {launcher}"
48
+ git_elog = _console_script(scripts, "git-elog")
49
+ elog = _console_script(scripts, "elog")
50
+ assert git_elog.is_file(), f"missing installed launcher: {git_elog}"
51
+ assert elog.is_file(), f"missing installed alias: {elog}"
50
52
  # Empty repo so --json is a finished walk with no commits (exit 0).
51
53
  subprocess.run(["git", "init"], cwd=elsewhere, check=True, capture_output=True)
52
54
  subprocess.run(
@@ -63,13 +65,13 @@ def installed_env(
63
65
  )
64
66
  env = os.environ.copy()
65
67
  env["PATH"] = str(scripts) + os.pathsep + env.get("PATH", "")
66
- return elsewhere, launcher, env
68
+ return elsewhere, git_elog, elog, env
67
69
 
68
70
 
69
71
  def test_git_elog_help_from_outside_source(
70
- installed_env: tuple[Path, Path, dict[str, str]],
72
+ installed_env: tuple[Path, Path, Path, dict[str, str]],
71
73
  ) -> None:
72
- cwd, launcher, env = installed_env
74
+ cwd, launcher, _elog, env = installed_env
73
75
  completed = subprocess.run(
74
76
  [str(launcher), "--help"],
75
77
  cwd=cwd,
@@ -82,10 +84,27 @@ def test_git_elog_help_from_outside_source(
82
84
  assert "--json" in completed.stdout
83
85
 
84
86
 
87
+ def test_elog_alias_help_from_outside_source(
88
+ installed_env: tuple[Path, Path, Path, dict[str, str]],
89
+ ) -> None:
90
+ cwd, _git_elog, elog, env = installed_env
91
+ completed = subprocess.run(
92
+ [str(elog), "--help"],
93
+ cwd=cwd,
94
+ env=env,
95
+ capture_output=True,
96
+ text=True,
97
+ check=False,
98
+ )
99
+ assert completed.returncode == 0, completed.stderr
100
+ assert "--json" in completed.stdout
101
+ assert "Usage: elog" in completed.stdout
102
+
103
+
85
104
  def test_git_elog_json_from_outside_source(
86
- installed_env: tuple[Path, Path, dict[str, str]],
105
+ installed_env: tuple[Path, Path, Path, dict[str, str]],
87
106
  ) -> None:
88
- cwd, launcher, env = installed_env
107
+ cwd, launcher, _elog, env = installed_env
89
108
  completed = subprocess.run(
90
109
  [str(launcher), "--json"],
91
110
  cwd=cwd,
@@ -102,9 +121,9 @@ def test_git_elog_json_from_outside_source(
102
121
 
103
122
 
104
123
  def test_git_elog_subcommand_when_git_available(
105
- installed_env: tuple[Path, Path, dict[str, str]],
124
+ installed_env: tuple[Path, Path, Path, dict[str, str]],
106
125
  ) -> None:
107
- cwd, _launcher, env = installed_env
126
+ cwd, _launcher, _elog, env = installed_env
108
127
  git = subprocess.run(
109
128
  ["git", "--version"],
110
129
  cwd=cwd,
@@ -99,6 +99,32 @@ def test_custom_config_prefix_refs(tmp_path: Path) -> None:
99
99
  assert "metadata.profile.partial" in _codes(result)
100
100
 
101
101
 
102
+ def test_custom_config_suffix_bracket_refs(tmp_path: Path) -> None:
103
+ path = tmp_path / "profile.json"
104
+ path.write_text(
105
+ json.dumps(
106
+ {
107
+ "conventional": True,
108
+ "conventionalLocations": ["title"],
109
+ "references": True,
110
+ "referenceLocations": ["suffix", "refs"],
111
+ "identifierFamily": "jira-id",
112
+ }
113
+ ),
114
+ encoding="utf-8",
115
+ )
116
+ result = analyze_commit(
117
+ "feat: Add a narrated video of the UI panel [PD10020-1284]",
118
+ "",
119
+ config_path=path,
120
+ )
121
+ assert result.references_matched
122
+ assert [claim.value for claim in result.claims_for("refs")] == ["PD10020-1284"]
123
+ assert result.display is not None
124
+ assert result.display.title == "Add a narrated video of the UI panel"
125
+ assert [ref.id for ref in result.display.refs] == ["PD10020-1284"]
126
+
127
+
102
128
  def test_resolve_trailer_default_display() -> None:
103
129
  config = resolve_config(profile="trailer")
104
130
  assert config.display == "trailer-precedence"
File without changes
File without changes