pdfly 0.3.0__tar.gz → 0.3.2__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 (60) hide show
  1. pdfly-0.3.0/.github/workflows/release.yaml → pdfly-0.3.2/.github/workflows/create-github-release.yaml +7 -28
  2. {pdfly-0.3.0 → pdfly-0.3.2}/.github/workflows/github-ci.yaml +1 -1
  3. pdfly-0.3.2/.github/workflows/publish-to-pypi.yaml +35 -0
  4. pdfly-0.3.2/.github/workflows/release.yaml +47 -0
  5. {pdfly-0.3.0 → pdfly-0.3.2}/.gitignore +4 -0
  6. {pdfly-0.3.0 → pdfly-0.3.2}/.pre-commit-config.yaml +3 -1
  7. {pdfly-0.3.0 → pdfly-0.3.2}/CHANGELOG.md +20 -0
  8. {pdfly-0.3.0 → pdfly-0.3.2}/Makefile +4 -0
  9. {pdfly-0.3.0 → pdfly-0.3.2}/PKG-INFO +1 -1
  10. pdfly-0.3.2/make_release.py +377 -0
  11. pdfly-0.3.2/pdfly/_version.py +1 -0
  12. {pdfly-0.3.0 → pdfly-0.3.2}/pdfly/up2.py +1 -1
  13. {pdfly-0.3.0 → pdfly-0.3.2}/pyproject.toml +1 -0
  14. {pdfly-0.3.0 → pdfly-0.3.2}/requirements/dev.in +2 -0
  15. {pdfly-0.3.0 → pdfly-0.3.2}/requirements/dev.txt +12 -1
  16. pdfly-0.3.0/pdfly/_version.py +0 -1
  17. {pdfly-0.3.0 → pdfly-0.3.2}/.github/dependabot.yml +0 -0
  18. {pdfly-0.3.0 → pdfly-0.3.2}/.gitmodules +0 -0
  19. {pdfly-0.3.0 → pdfly-0.3.2}/.isort.cfg +0 -0
  20. {pdfly-0.3.0 → pdfly-0.3.2}/.readthedocs.yaml +0 -0
  21. {pdfly-0.3.0 → pdfly-0.3.2}/CONTRIBUTORS.md +0 -0
  22. {pdfly-0.3.0 → pdfly-0.3.2}/LICENSE +0 -0
  23. {pdfly-0.3.0 → pdfly-0.3.2}/README.md +0 -0
  24. {pdfly-0.3.0 → pdfly-0.3.2}/docs/Makefile +0 -0
  25. {pdfly-0.3.0 → pdfly-0.3.2}/docs/conf.py +0 -0
  26. {pdfly-0.3.0 → pdfly-0.3.2}/docs/index.rst +0 -0
  27. {pdfly-0.3.0 → pdfly-0.3.2}/docs/make.bat +0 -0
  28. {pdfly-0.3.0 → pdfly-0.3.2}/docs/meta/project-governance.md +0 -0
  29. {pdfly-0.3.0 → pdfly-0.3.2}/docs/user/installation.md +0 -0
  30. {pdfly-0.3.0 → pdfly-0.3.2}/docs/user/subcommand-cat.md +0 -0
  31. {pdfly-0.3.0 → pdfly-0.3.2}/docs/user/subcommand-meta.md +0 -0
  32. {pdfly-0.3.0 → pdfly-0.3.2}/docs/user/subcommand-x2pdf.md +0 -0
  33. {pdfly-0.3.0 → pdfly-0.3.2}/mypy.ini +0 -0
  34. {pdfly-0.3.0 → pdfly-0.3.2}/pdfly/__init__.py +0 -0
  35. {pdfly-0.3.0 → pdfly-0.3.2}/pdfly/__main__.py +0 -0
  36. {pdfly-0.3.0 → pdfly-0.3.2}/pdfly/_utils.py +0 -0
  37. {pdfly-0.3.0 → pdfly-0.3.2}/pdfly/cat.py +0 -0
  38. {pdfly-0.3.0 → pdfly-0.3.2}/pdfly/cli.py +0 -0
  39. {pdfly-0.3.0 → pdfly-0.3.2}/pdfly/compress.py +0 -0
  40. {pdfly-0.3.0 → pdfly-0.3.2}/pdfly/extract_images.py +0 -0
  41. {pdfly-0.3.0 → pdfly-0.3.2}/pdfly/metadata.py +0 -0
  42. {pdfly-0.3.0 → pdfly-0.3.2}/pdfly/pagemeta.py +0 -0
  43. {pdfly-0.3.0 → pdfly-0.3.2}/pdfly/x2pdf.py +0 -0
  44. {pdfly-0.3.0 → pdfly-0.3.2}/requirements/ci.in +0 -0
  45. {pdfly-0.3.0 → pdfly-0.3.2}/requirements/ci.txt +0 -0
  46. {pdfly-0.3.0 → pdfly-0.3.2}/requirements/docs.in +0 -0
  47. {pdfly-0.3.0 → pdfly-0.3.2}/requirements/docs.txt +0 -0
  48. {pdfly-0.3.0 → pdfly-0.3.2}/resources/GeoBase_NHNC1_Data_Model_UML_EN.pdf +0 -0
  49. {pdfly-0.3.0 → pdfly-0.3.2}/resources/baleines.jpg +0 -0
  50. {pdfly-0.3.0 → pdfly-0.3.2}/resources/box.pdf +0 -0
  51. {pdfly-0.3.0 → pdfly-0.3.2}/resources/jpeg.pdf +0 -0
  52. {pdfly-0.3.0 → pdfly-0.3.2}/resources/pythonknight.png +0 -0
  53. {pdfly-0.3.0 → pdfly-0.3.2}/setup.cfg +0 -0
  54. {pdfly-0.3.0 → pdfly-0.3.2}/setup.py +0 -0
  55. {pdfly-0.3.0 → pdfly-0.3.2}/tests/__init__.py +0 -0
  56. {pdfly-0.3.0 → pdfly-0.3.2}/tests/conftest.py +0 -0
  57. {pdfly-0.3.0 → pdfly-0.3.2}/tests/test_cat.py +0 -0
  58. {pdfly-0.3.0 → pdfly-0.3.2}/tests/test_cli.py +0 -0
  59. {pdfly-0.3.0 → pdfly-0.3.2}/tests/test_extract_images.py +0 -0
  60. {pdfly-0.3.0 → pdfly-0.3.2}/tests/test_x2pdf.py +0 -0
@@ -1,42 +1,21 @@
1
- # This action assumes that there is a REL-commit which already has a
2
- # Markdown-formatted git tag. Hence the CHANGELOG is already adjusted
3
- # and it's decided what should be in the release.
4
- # This action only ensures the release is done with the proper contents
5
- # and that it's announced with a Github release.
6
- name: Publish Python Package to PyPI
1
+ name: Create a GitHub release page
2
+
7
3
  on:
8
4
  push:
9
5
  tags:
10
6
  - '*.*.*'
7
+ workflow_dispatch:
8
+
9
+ permissions:
10
+ contents: write
11
11
 
12
12
  jobs:
13
13
  build_and_publish:
14
- name: Publish a new version
14
+ name: Create a GitHub release page
15
15
  runs-on: ubuntu-latest
16
-
17
16
  steps:
18
- # Ensure it's on PyPI
19
17
  - name: Checkout Repository
20
18
  uses: actions/checkout@v4
21
-
22
- - name: Set up Python
23
- uses: actions/setup-python@v5
24
- with:
25
- python-version: 3.x
26
-
27
- - name: Install Flit
28
- run: |
29
- python -m pip install --upgrade pip
30
- pip install flit
31
-
32
- - name: Publish Package to PyPI🚀
33
- env:
34
- FLIT_USERNAME: '__token__'
35
- FLIT_PASSWORD: ${{ secrets.FLIT_PASSWORD }}
36
- run: |
37
- flit publish
38
-
39
- # Create the Github Page
40
19
  - name: Prepare variables
41
20
  id: prepare_variables
42
21
  run: |
@@ -54,7 +54,7 @@ jobs:
54
54
  submodules: 'recursive'
55
55
  - name: Cache Downloaded Files
56
56
  id: cache-downloaded-files
57
- uses: actions/cache@v3
57
+ uses: actions/cache@v4
58
58
  with:
59
59
  path: '**/tests/pdf_cache/*'
60
60
  key: cache-downloaded-files
@@ -0,0 +1,35 @@
1
+ name: Publish Python Package to PyPI
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - '*.*.*'
7
+ workflow_dispatch:
8
+
9
+ permissions:
10
+ contents: write
11
+
12
+ jobs:
13
+ build_and_publish:
14
+ name: Publish a new version
15
+ runs-on: ubuntu-latest
16
+ steps:
17
+ - name: Set up Python
18
+ uses: actions/setup-python@v5
19
+ with:
20
+ python-version: 3.x
21
+
22
+ - name: Install Flit
23
+ run: |
24
+ python -m pip install --upgrade pip
25
+ pip install flit
26
+
27
+ - name: Checkout Repository
28
+ uses: actions/checkout@v4
29
+
30
+ - name: Publish Package to PyPI🚀
31
+ env:
32
+ FLIT_USERNAME: '__token__'
33
+ FLIT_PASSWORD: ${{ secrets.FLIT_PASSWORD }}
34
+ run: |
35
+ flit publish
@@ -0,0 +1,47 @@
1
+ # This action assumes that there is a REL-commit which already has a
2
+ # Markdown-formatted git tag. Hence the CHANGELOG is already adjusted
3
+ # and it's decided what should be in the release.
4
+ # This action only ensures the release is done with the proper contents
5
+ # and that it's announced with a Github release.
6
+ name: Create git tag
7
+ on:
8
+ push:
9
+ branches:
10
+ - main
11
+
12
+ permissions:
13
+ contents: write
14
+
15
+ jobs:
16
+ build_and_publish:
17
+ name: Publish a new version
18
+ runs-on: ubuntu-latest
19
+ if: "${{ startsWith(github.event.head_commit.message, 'REL: ') }}"
20
+ steps:
21
+ - name: Checkout Repository
22
+ uses: actions/checkout@v4
23
+
24
+ - name: Extract version from commit message
25
+ id: extract_version
26
+ run: |
27
+ VERSION=$(echo "${{ github.event.head_commit.message }}" | grep -oP '(?<=REL: )\d+\.\d+\.\d+')
28
+ echo "version=$VERSION" >> $GITHUB_OUTPUT
29
+
30
+ - name: Extract tag message from commit message
31
+ id: extract_message
32
+ run: |
33
+ VERSION="${{ steps.extract_version.outputs.version }}"
34
+ delimiter="$(openssl rand -hex 8)"
35
+ MESSAGE=$(echo "${{ github.event.head_commit.message }}" | sed "0,/REL: $VERSION/s///" )
36
+ echo "message<<${delimiter}" >> $GITHUB_OUTPUT
37
+ echo "$MESSAGE" >> $GITHUB_OUTPUT
38
+ echo "${delimiter}" >> $GITHUB_OUTPUT
39
+
40
+ - name: Create Git Tag
41
+ run: |
42
+ VERSION="${{ steps.extract_version.outputs.version }}"
43
+ MESSAGE="${{ steps.extract_message.outputs.message }}"
44
+ git config user.name github-actions
45
+ git config user.email github-actions@github.com
46
+ git tag "$VERSION" -m "$MESSAGE"
47
+ git push origin $VERSION
@@ -134,3 +134,7 @@ dmypy.json
134
134
  # Documentation files copied when building:
135
135
  docs/meta/CHANGELOG.md
136
136
  docs/meta/CONTRIBUTORS.md
137
+
138
+ # 'make release' creates those files:
139
+ RELEASE_COMMIT_MSG.md
140
+ RELEASE_TAG_MSG.md
@@ -48,5 +48,7 @@ repos:
48
48
  rev: 'v1.7.1'
49
49
  hooks:
50
50
  - id: mypy
51
- files: ^pypdf/.*
51
+ files: ^pdfly/.*
52
52
  args: [--ignore-missing-imports]
53
+ additional_dependencies:
54
+ - "pydantic>=1.10.4"
@@ -1,5 +1,25 @@
1
1
  # CHANGELOG
2
2
 
3
+ ## Version 0.3.2, 2024-04-14
4
+
5
+ ### Developer Experience (DEV)
6
+ - Decouple git tag / PyPI release / Github release page (#49, #50)
7
+
8
+
9
+ [Full Changelog](https://github.com/py-pdf/pdfly/compare/0.3.1...0.3.2)
10
+
11
+ ## Version 0.3.1, 2024-03-29
12
+
13
+ ### Maintenance (MAINT)
14
+ - Update pypdf usage (#48)
15
+
16
+ ### Developer Experience (DEV)
17
+ - Release via REL commit (#48)
18
+ - Fix mypy issues
19
+ - Add make_release.py
20
+
21
+ [Full Changelog](https://github.com/py-pdf/pdfly/compare/0.3.0...0.3.1)
22
+
3
23
  ## Version 0.3.0, 2023-12-17
4
24
 
5
25
  ### New Features (ENH)
@@ -4,6 +4,10 @@ maint:
4
4
  pip-compile -U requirements/dev.in
5
5
  pip-compile -U requirements/docs.in
6
6
 
7
+ release:
8
+ python make_release.py
9
+ git commit -eF RELEASE_COMMIT_MSG.md
10
+
7
11
  upload:
8
12
  make clean
9
13
  flit publish
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pdfly
3
- Version: 0.3.0
3
+ Version: 0.3.2
4
4
  Summary: A pure-python CLI application to manipulate PDF files
5
5
  Author-email: Martin Thoma <info@martin-thoma.de>
6
6
  Maintainer-email: Martin Thoma <info@martin-thoma.de>
@@ -0,0 +1,377 @@
1
+ """Internal tool to update the changelog."""
2
+
3
+ import json
4
+ import subprocess
5
+ import urllib.request
6
+ from dataclasses import dataclass
7
+ from datetime import datetime, timezone
8
+ from typing import Any, Dict, List, Tuple
9
+
10
+ from rich.prompt import Prompt
11
+
12
+
13
+ @dataclass(frozen=True)
14
+ class Change:
15
+ """Capture the data of a git commit."""
16
+
17
+ commit_hash: str
18
+ prefix: str
19
+ message: str
20
+ author: str
21
+ author_login: str
22
+
23
+
24
+ def main(changelog_path: str) -> None:
25
+ """
26
+ Create a changelog.
27
+
28
+ Args:
29
+ changelog_path: The location of the CHANGELOG file
30
+ """
31
+ changelog = get_changelog(changelog_path)
32
+ git_tag = get_most_recent_git_tag()
33
+ changes, changes_with_author = get_formatted_changes(git_tag)
34
+ if changes == "":
35
+ print("No changes")
36
+ return
37
+
38
+ new_version = version_bump(git_tag)
39
+ new_version = get_version_interactive(new_version, changes)
40
+ adjust_version_py(new_version)
41
+
42
+ today = datetime.now(tz=timezone.utc)
43
+ header = f"## Version {new_version}, {today:%Y-%m-%d}\n"
44
+ url = f"https://github.com/py-pdf/pdfly/compare/{git_tag}...{new_version}"
45
+ trailer = f"\n[Full Changelog]({url})\n\n"
46
+ new_entry = header + changes + trailer
47
+ print(new_entry)
48
+ write_commit_msg_file(new_version, changes_with_author + trailer)
49
+ write_release_msg_file(new_version, changes_with_author + trailer, today)
50
+
51
+ # Make the script idempotent by checking if the new entry is already in the changelog
52
+ if new_entry in changelog:
53
+ print("Changelog is already up-to-date!")
54
+ return
55
+
56
+ new_changelog = "# CHANGELOG\n\n" + new_entry + strip_header(changelog)
57
+ write_changelog(new_changelog, changelog_path)
58
+ print_instructions(new_version)
59
+
60
+
61
+ def print_instructions(new_version: str) -> None:
62
+ """Print release instructions."""
63
+ print("=" * 80)
64
+ print(f"☑ _version.py was adjusted to '{new_version}'")
65
+ print("☑ CHANGELOG.md was adjusted")
66
+ print("")
67
+ print("Now run:")
68
+ print(" git commit -eF RELEASE_COMMIT_MSG.md")
69
+ print(f" git tag -s {new_version} -eF RELEASE_TAG_MSG.md")
70
+ print(" git push")
71
+ print(" git push --tags")
72
+
73
+
74
+ def adjust_version_py(version: str) -> None:
75
+ """Adjust the __version__ string."""
76
+ with open("pdfly/_version.py", "w") as fp:
77
+ fp.write(f'__version__ = "{version}"\n')
78
+
79
+
80
+ def get_version_interactive(new_version: str, changes: str) -> str:
81
+ """Get the new __version__ interactively."""
82
+ print("The changes are:")
83
+ print(changes)
84
+ orig = new_version
85
+ new_version = Prompt.ask("New semantic version", default=orig)
86
+ while not is_semantic_version(new_version):
87
+ new_version = Prompt.ask(
88
+ "That was not a semantic version. Please enter a semantic version",
89
+ default=orig,
90
+ )
91
+ return new_version
92
+
93
+
94
+ def is_semantic_version(version: str) -> bool:
95
+ """Check if the given version is a semantic version."""
96
+ # It's not so important to cover the edge-cases like pre-releases
97
+ # This is meant for pdfly only and we don't make pre-releases
98
+ if version.count(".") != 2:
99
+ return False
100
+ try:
101
+ return bool([int(part) for part in version.split(".")])
102
+ except Exception:
103
+ return False
104
+
105
+
106
+ def write_commit_msg_file(new_version: str, commit_changes: str) -> None:
107
+ """
108
+ Write a file that can be used as a commit message.
109
+
110
+ Like this:
111
+
112
+ git commit -eF RELEASE_COMMIT_MSG.md && git push
113
+ """
114
+ with open("RELEASE_COMMIT_MSG.md", "w") as fp:
115
+ fp.write(f"REL: {new_version}\n\n")
116
+ fp.write("## What's new\n")
117
+ fp.write(commit_changes)
118
+
119
+
120
+ def write_release_msg_file(
121
+ new_version: str, commit_changes: str, today: datetime
122
+ ) -> None:
123
+ """
124
+ Write a file that can be used as a git tag message.
125
+
126
+ Like this:
127
+
128
+ git tag -eF RELEASE_TAG_MSG.md && git push
129
+ """
130
+ with open("RELEASE_TAG_MSG.md", "w") as fp:
131
+ fp.write(f"Version {new_version}, {today:%Y-%m-%d}\n\n")
132
+ fp.write("## What's new\n")
133
+ fp.write(commit_changes)
134
+
135
+
136
+ def strip_header(md: str) -> str:
137
+ """Remove the 'CHANGELOG' header."""
138
+ return md.lstrip("# CHANGELOG").lstrip() # noqa
139
+
140
+
141
+ def version_bump(git_tag: str) -> str:
142
+ """
143
+ Increase the patch version of the git tag by one.
144
+
145
+ Args:
146
+ git_tag: Old version tag
147
+
148
+ Returns:
149
+ The new version where the patch version is bumped.
150
+ """
151
+ # just assume a patch version change
152
+ major, minor, patch = git_tag.split(".")
153
+ return f"{major}.{minor}.{int(patch) + 1}"
154
+
155
+
156
+ def get_changelog(changelog_path: str) -> str:
157
+ """
158
+ Read the changelog.
159
+
160
+ Args:
161
+ changelog_path: Path to the CHANGELOG file
162
+
163
+ Returns:
164
+ Data of the CHANGELOG
165
+ """
166
+ with open(changelog_path) as fh:
167
+ changelog = fh.read()
168
+ return changelog
169
+
170
+
171
+ def write_changelog(new_changelog: str, changelog_path: str) -> None:
172
+ """
173
+ Write the changelog.
174
+
175
+ Args:
176
+ new_changelog: Contents of the new CHANGELOG
177
+ changelog_path: Path where the CHANGELOG file is
178
+ """
179
+ with open(changelog_path, "w") as fh:
180
+ fh.write(new_changelog)
181
+
182
+
183
+ def get_formatted_changes(git_tag: str) -> Tuple[str, str]:
184
+ """
185
+ Format the changes done since the last tag.
186
+
187
+ Args:
188
+ git_tag: the reference tag
189
+
190
+ Returns:
191
+ Changes done since git_tag
192
+ """
193
+ commits = get_git_commits_since_tag(git_tag)
194
+
195
+ # Group by prefix
196
+ grouped: Dict[str, List[Dict[str, Any]]] = {}
197
+ for commit in commits:
198
+ if commit.prefix not in grouped:
199
+ grouped[commit.prefix] = []
200
+ grouped[commit.prefix].append(
201
+ {"msg": commit.message, "author": commit.author_login}
202
+ )
203
+
204
+ # Order prefixes
205
+ order = [
206
+ "SEC",
207
+ "DEP",
208
+ "ENH",
209
+ "PI",
210
+ "BUG",
211
+ "ROB",
212
+ "DOC",
213
+ "DEV",
214
+ "CI",
215
+ "MAINT",
216
+ "TST",
217
+ "STY",
218
+ ]
219
+ abbrev2long = {
220
+ "SEC": "Security",
221
+ "DEP": "Deprecations",
222
+ "ENH": "New Features",
223
+ "BUG": "Bug Fixes",
224
+ "ROB": "Robustness",
225
+ "DOC": "Documentation",
226
+ "DEV": "Developer Experience",
227
+ "CI": "Continuous Integration",
228
+ "MAINT": "Maintenance",
229
+ "TST": "Testing",
230
+ "STY": "Code Style",
231
+ "PI": "Performance Improvements",
232
+ }
233
+
234
+ # Create output
235
+ output = ""
236
+ output_with_user = ""
237
+ for prefix in order:
238
+ if prefix not in grouped:
239
+ continue
240
+ tmp = f"\n### {abbrev2long[prefix]} ({prefix})\n" # header
241
+ output += tmp
242
+ output_with_user += tmp
243
+ for commit_dict in grouped[prefix]:
244
+ output += f"- {commit_dict['msg']}\n"
245
+ output_with_user += (
246
+ f"- {commit_dict['msg']} by @{commit_dict['author']}\n"
247
+ )
248
+ del grouped[prefix]
249
+
250
+ if grouped:
251
+ output += "\n### Other\n"
252
+ for prefix in grouped:
253
+ output += f"- {prefix}: {grouped[prefix]}\n"
254
+
255
+ return output, output_with_user
256
+
257
+
258
+ def get_most_recent_git_tag() -> str:
259
+ """
260
+ Get the git tag most recently created.
261
+
262
+ Returns:
263
+ Most recently created git tag.
264
+ """
265
+ git_tag = str(
266
+ subprocess.check_output(
267
+ ["git", "describe", "--abbrev=0"], stderr=subprocess.STDOUT
268
+ )
269
+ ).strip("'b\\n")
270
+ return git_tag
271
+
272
+
273
+ def get_author_mapping(line_count: int) -> Dict[str, str]:
274
+ """
275
+ Get the authors for each commit.
276
+
277
+ Args:
278
+ line_count: Number of lines from Git log output. Used for determining how
279
+ many commits to fetch.
280
+
281
+ Returns:
282
+ A mapping of long commit hashes to author login handles.
283
+ """
284
+ per_page = min(line_count, 100)
285
+ page = 1
286
+ mapping: Dict[str, str] = {}
287
+ for _ in range(0, line_count, per_page):
288
+ with urllib.request.urlopen( # noqa: S310
289
+ f"https://api.github.com/repos/py-pdf/pdfly/commits?per_page={per_page}&page={page}"
290
+ ) as response:
291
+ commits = json.loads(response.read())
292
+ page += 1
293
+ for commit in commits:
294
+ print(commit)
295
+ if commit["author"]:
296
+ gh_handle = commit["author"]["login"]
297
+ else:
298
+ # This is not perfect, but better than the other option
299
+ gh_handle = commit["commit"]["author"]["name"].replace(" ", "")
300
+ mapping[commit["sha"]] = gh_handle
301
+ return mapping
302
+
303
+
304
+ def get_git_commits_since_tag(git_tag: str) -> List[Change]:
305
+ """
306
+ Get all commits since the last tag.
307
+
308
+ Args:
309
+ git_tag: Reference tag from which the changes to the current commit are
310
+ fetched.
311
+
312
+ Returns:
313
+ List of all changes since git_tag.
314
+ """
315
+ commits = str(
316
+ subprocess.check_output(
317
+ [
318
+ "git",
319
+ "--no-pager",
320
+ "log",
321
+ f"{git_tag}..HEAD",
322
+ '--pretty=format:"%H:::%s:::%aN"',
323
+ ],
324
+ stderr=subprocess.STDOUT,
325
+ )
326
+ ).strip("'b\\n")
327
+ lines = commits.split("\\n")
328
+ authors = get_author_mapping(len(lines))
329
+ return [parse_commit_line(line, authors) for line in lines if line != ""]
330
+
331
+
332
+ def parse_commit_line(line: str, authors: Dict[str, str]) -> Change:
333
+ """
334
+ Parse the first line of a git commit message.
335
+
336
+ Args:
337
+ line: The first line of a git commit message.
338
+
339
+ Returns:
340
+ The parsed Change object
341
+
342
+ Raises:
343
+ ValueError: The commit line is not well-structured
344
+ """
345
+ parts = line.split(":::")
346
+ if len(parts) != 3:
347
+ raise ValueError(f"Invalid commit line: '{line}'")
348
+ commit_hash, rest, author = parts
349
+ if ":" in rest:
350
+ prefix, message = rest.split(":", 1)
351
+ else:
352
+ prefix = ""
353
+ message = rest
354
+
355
+ # Standardize
356
+ message.strip()
357
+ commit_hash = commit_hash.strip('"')
358
+
359
+ if author.endswith('"'):
360
+ author = author[:-1]
361
+ author_login = authors[commit_hash]
362
+
363
+ prefix = prefix.strip()
364
+ if prefix == "DOCS":
365
+ prefix = "DOC"
366
+
367
+ return Change(
368
+ commit_hash=commit_hash,
369
+ prefix=prefix,
370
+ message=message,
371
+ author=author,
372
+ author_login=author_login,
373
+ )
374
+
375
+
376
+ if __name__ == "__main__":
377
+ main("CHANGELOG.md")
@@ -0,0 +1 @@
1
+ __version__ = "0.3.2"
@@ -18,7 +18,7 @@ def main(pdf: Path, output: Path) -> None:
18
18
  for i in range(0, len(reader.pages) - 1, 2):
19
19
  lhs = reader.pages[i]
20
20
  rhs = reader.pages[i + 1]
21
- lhs.mergeTranslatedPage(rhs, float(lhs.mediabox.right), 0, True)
21
+ lhs.merge_translated_page(rhs, float(lhs.mediabox.right), 0, True)
22
22
  writer.add_page(lhs)
23
23
  print(str(i) + " ")
24
24
  sys.stdout.flush()
@@ -143,3 +143,4 @@ max-complexity = 20 # Recommended: 10
143
143
 
144
144
  [tool.ruff.per-file-ignores]
145
145
  "sample-files/*" = ["D100", "INP001", "FA102", "I001"]
146
+ "make_release.py" = ["T201", "S603", "S607"]
@@ -1,9 +1,11 @@
1
1
  black
2
2
  pip-tools
3
3
  pre-commit>=3.2.0
4
+ pydantic
4
5
  pytest
5
6
  pytest-cov
6
7
  pytest-socket
7
8
  pytest-timeout
9
+ rich
8
10
  twine
9
11
  wheel
@@ -8,6 +8,8 @@ aiohttp==3.9.1
8
8
  # via black
9
9
  aiosignal==1.3.1
10
10
  # via aiohttp
11
+ annotated-types==0.6.0
12
+ # via pydantic
11
13
  async-timeout==4.0.3
12
14
  # via aiohttp
13
15
  attrs==23.1.0
@@ -106,6 +108,10 @@ pre-commit==3.5.0
106
108
  # via -r requirements/dev.in
107
109
  pycparser==2.21
108
110
  # via cffi
111
+ pydantic==2.5.2
112
+ # via -r requirements/dev.in
113
+ pydantic-core==2.14.5
114
+ # via pydantic
109
115
  pygments==2.17.2
110
116
  # via
111
117
  # readme-renderer
@@ -137,7 +143,9 @@ requests-toolbelt==1.0.0
137
143
  rfc3986==2.0.0
138
144
  # via twine
139
145
  rich==13.7.0
140
- # via twine
146
+ # via
147
+ # -r requirements/dev.in
148
+ # twine
141
149
  secretstorage==3.3.3
142
150
  # via keyring
143
151
  tomli==2.0.1
@@ -152,7 +160,10 @@ twine==4.0.2
152
160
  # via -r requirements/dev.in
153
161
  typing-extensions==4.9.0
154
162
  # via
163
+ # annotated-types
155
164
  # black
165
+ # pydantic
166
+ # pydantic-core
156
167
  # rich
157
168
  urllib3==2.1.0
158
169
  # via
@@ -1 +0,0 @@
1
- __version__ = "0.3.0"
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
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
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
File without changes
File without changes
File without changes