spreen-pr 0.1.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.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 - present @hayat01sh1da
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,112 @@
1
+ Metadata-Version: 2.4
2
+ Name: spreen-pr
3
+ Version: 0.1.0
4
+ Summary: Print a pull request title with labels derived from the topic branch name.
5
+ Author: hayat01sh1da
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/hayat01sh1da/spreen-pr
8
+ Project-URL: Source, https://github.com/hayat01sh1da/spreen-pr
9
+ Project-URL: Changelog, https://github.com/hayat01sh1da/spreen-pr/blob/master/CHANGELOG.md
10
+ Keywords: github,pull-request,title,label,branch
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Environment :: Console
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Operating System :: OS Independent
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Topic :: Software Development :: Version Control :: Git
17
+ Requires-Python: >=3.10
18
+ Description-Content-Type: text/markdown
19
+ License-File: LICENSE.txt
20
+ Dynamic: license-file
21
+
22
+ ## 1. Environment
23
+
24
+ - Python 3.14.6
25
+ - pip 26.1.2
26
+
27
+ ## 2. Installation
28
+
29
+ ```command
30
+ $ pipx install spreen-pr
31
+ ```
32
+
33
+ (`pip install spreen-pr` works too if you prefer managing the environment yourself.)
34
+
35
+ For development, install the dependencies via requirements.txt:
36
+
37
+ ```command
38
+ $ pip install -r requirements.txt
39
+ ```
40
+
41
+ ## 3. Execution
42
+
43
+ ```command
44
+ $ pr-title hayat01sh1da/issue-89/service/improve-onboarding-flow
45
+ [service] Improve Onboarding Flow
46
+ ```
47
+
48
+ With no argument, the current git branch is used:
49
+
50
+ ```command
51
+ $ git switch hayat01sh1da/issue-90/hotfix/service/fix_login_crash
52
+ $ pr-title
53
+ [Hotfix][service] Fix Login Crash
54
+ ```
55
+
56
+ `--format json` exposes the labels separately for scripting and CI:
57
+
58
+ ```command
59
+ $ pr-title hayat01sh1da/issue-90/hotfix/service/fix_login_crash --format json
60
+ {"title": "[Hotfix][service] Fix Login Crash", "labels": ["Hotfix", "service"]}
61
+ ```
62
+
63
+ As a library:
64
+
65
+ ```python
66
+ from spreen_pr import Application
67
+
68
+ Application.run(branch_name='hayat01sh1da/issue-89/service/improve-onboarding-flow')
69
+ # => '[service] Improve Onboarding Flow'
70
+
71
+ application = Application(branch_name='hayat01sh1da/issue-90/hotfix/service/fix_login_crash')
72
+ application.title # => '[Hotfix][service] Fix Login Crash'
73
+ application.labels # => ['Hotfix', 'service']
74
+ ```
75
+
76
+ ## 4. Unit Test
77
+
78
+ ```command
79
+ $ pytest
80
+ ============================= test session starts ==============================
81
+ platform linux -- Python 3.14.6, pytest-9.1.1, pluggy-1.6.0
82
+ rootdir: spreen-pr/pypi
83
+ configfile: pyproject.toml
84
+ collected 15 items
85
+
86
+ test/test_application.py ......... [ 60%]
87
+ test/test_cli.py ...... [100%]
88
+
89
+ ============================== 15 passed in 0.12s ===============================
90
+ ```
91
+
92
+ ## 5. Static Code Analysis
93
+
94
+ ```command
95
+ $ flake8 .
96
+ $ autoflake8 --in-place --remove-duplicate-keys --remove-unused-variables --recursive .
97
+ $ autopep8 --in-place --aggressive --aggressive --recursive .
98
+ ```
99
+
100
+ ## 6. Type Checks
101
+
102
+ ```command
103
+ $ mypy .
104
+ Success: no issues found in 6 source files
105
+ ```
106
+
107
+ ## 7. Build
108
+
109
+ ```command
110
+ $ python -m build
111
+ $ pipx install ./dist/spreen_pr-0.1.0-py3-none-any.whl
112
+ ```
@@ -0,0 +1,91 @@
1
+ ## 1. Environment
2
+
3
+ - Python 3.14.6
4
+ - pip 26.1.2
5
+
6
+ ## 2. Installation
7
+
8
+ ```command
9
+ $ pipx install spreen-pr
10
+ ```
11
+
12
+ (`pip install spreen-pr` works too if you prefer managing the environment yourself.)
13
+
14
+ For development, install the dependencies via requirements.txt:
15
+
16
+ ```command
17
+ $ pip install -r requirements.txt
18
+ ```
19
+
20
+ ## 3. Execution
21
+
22
+ ```command
23
+ $ pr-title hayat01sh1da/issue-89/service/improve-onboarding-flow
24
+ [service] Improve Onboarding Flow
25
+ ```
26
+
27
+ With no argument, the current git branch is used:
28
+
29
+ ```command
30
+ $ git switch hayat01sh1da/issue-90/hotfix/service/fix_login_crash
31
+ $ pr-title
32
+ [Hotfix][service] Fix Login Crash
33
+ ```
34
+
35
+ `--format json` exposes the labels separately for scripting and CI:
36
+
37
+ ```command
38
+ $ pr-title hayat01sh1da/issue-90/hotfix/service/fix_login_crash --format json
39
+ {"title": "[Hotfix][service] Fix Login Crash", "labels": ["Hotfix", "service"]}
40
+ ```
41
+
42
+ As a library:
43
+
44
+ ```python
45
+ from spreen_pr import Application
46
+
47
+ Application.run(branch_name='hayat01sh1da/issue-89/service/improve-onboarding-flow')
48
+ # => '[service] Improve Onboarding Flow'
49
+
50
+ application = Application(branch_name='hayat01sh1da/issue-90/hotfix/service/fix_login_crash')
51
+ application.title # => '[Hotfix][service] Fix Login Crash'
52
+ application.labels # => ['Hotfix', 'service']
53
+ ```
54
+
55
+ ## 4. Unit Test
56
+
57
+ ```command
58
+ $ pytest
59
+ ============================= test session starts ==============================
60
+ platform linux -- Python 3.14.6, pytest-9.1.1, pluggy-1.6.0
61
+ rootdir: spreen-pr/pypi
62
+ configfile: pyproject.toml
63
+ collected 15 items
64
+
65
+ test/test_application.py ......... [ 60%]
66
+ test/test_cli.py ...... [100%]
67
+
68
+ ============================== 15 passed in 0.12s ===============================
69
+ ```
70
+
71
+ ## 5. Static Code Analysis
72
+
73
+ ```command
74
+ $ flake8 .
75
+ $ autoflake8 --in-place --remove-duplicate-keys --remove-unused-variables --recursive .
76
+ $ autopep8 --in-place --aggressive --aggressive --recursive .
77
+ ```
78
+
79
+ ## 6. Type Checks
80
+
81
+ ```command
82
+ $ mypy .
83
+ Success: no issues found in 6 source files
84
+ ```
85
+
86
+ ## 7. Build
87
+
88
+ ```command
89
+ $ python -m build
90
+ $ pipx install ./dist/spreen_pr-0.1.0-py3-none-any.whl
91
+ ```
@@ -0,0 +1,47 @@
1
+ [build-system]
2
+ requires = ["setuptools>=77"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "spreen-pr"
7
+ version = "0.1.0"
8
+ description = "Print a pull request title with labels derived from the topic branch name."
9
+ readme = "README.md"
10
+ license = "MIT"
11
+ license-files = ["LICENSE.txt"]
12
+ authors = [{ name = "hayat01sh1da" }]
13
+ requires-python = ">=3.10"
14
+ keywords = ["github", "pull-request", "title", "label", "branch"]
15
+ classifiers = [
16
+ "Development Status :: 4 - Beta",
17
+ "Environment :: Console",
18
+ "Intended Audience :: Developers",
19
+ "Operating System :: OS Independent",
20
+ "Programming Language :: Python :: 3",
21
+ "Topic :: Software Development :: Version Control :: Git",
22
+ ]
23
+
24
+ [project.urls]
25
+ Homepage = "https://github.com/hayat01sh1da/spreen-pr"
26
+ Source = "https://github.com/hayat01sh1da/spreen-pr"
27
+ Changelog = "https://github.com/hayat01sh1da/spreen-pr/blob/master/CHANGELOG.md"
28
+
29
+ [project.scripts]
30
+ pr-title = "spreen_pr.cli:main"
31
+
32
+ [tool.setuptools.packages.find]
33
+ where = ["src"]
34
+
35
+ [tool.setuptools.package-data]
36
+ spreen_pr = ["py.typed"]
37
+
38
+ [tool.pytest.ini_options]
39
+ pythonpath = ["src"]
40
+ testpaths = ["test"]
41
+
42
+ [tool.mypy]
43
+ python_version = "3.14"
44
+ warn_return_any = true
45
+ warn_unused_configs = true
46
+ disallow_untyped_defs = true
47
+ mypy_path = "src"
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,13 @@
1
+ """Spreens a pull request — the falcon's stoop, then the preen: derives a
2
+ PR title and labels from a topic branch name shaped
3
+ ``{user}/{issue}/{category}/{summary}`` (with a hotfix special case) and
4
+ prints them as plain text or JSON."""
5
+
6
+ from .application import Application
7
+
8
+ __version__ = '0.1.0'
9
+
10
+ __all__ = [
11
+ 'Application',
12
+ '__version__',
13
+ ]
@@ -0,0 +1,49 @@
1
+ import re
2
+
3
+
4
+ class Application:
5
+ """Derives a pull request title and labels from a topic branch name
6
+ shaped ``{user}/{issue}/{category}/{summary}``, with the hotfix special
7
+ case ``{user}/{issue}/hotfix/{category}/{summary}``."""
8
+
9
+ EXPECTED_SHAPE = '{user}/{issue}/{category}/{summary}'
10
+
11
+ @classmethod
12
+ def run(cls, branch_name: str) -> str:
13
+ return cls(branch_name=branch_name).title
14
+
15
+ def __init__(self, branch_name: str) -> None:
16
+ self._branch_name = branch_name
17
+ self._segments = branch_name.split('/')
18
+ self._validate()
19
+
20
+ @property
21
+ def title(self) -> str:
22
+ labels = ''.join(f'[{label}]' for label in self.labels)
23
+ return f'{labels} {self.topic}'
24
+
25
+ @property
26
+ def labels(self) -> list[str]:
27
+ if self._is_hotfix():
28
+ return [self._segments[-3].capitalize(), self._segments[-2]]
29
+ return [self._segments[-2]]
30
+
31
+ @property
32
+ def topic(self) -> str:
33
+ words = re.split(r'[-_]', self._segments[-1])
34
+ return ' '.join(word.capitalize() for word in words)
35
+
36
+ def to_dict(self) -> dict[str, object]:
37
+ return {'title': self.title, 'labels': self.labels}
38
+
39
+ # private
40
+
41
+ def _is_hotfix(self) -> bool:
42
+ return self._segments[-3] == 'hotfix'
43
+
44
+ def _validate(self) -> None:
45
+ if len(self._segments) >= 3 and all(self._segments):
46
+ return
47
+ raise ValueError(
48
+ f'Branch name `{self._branch_name}` does not match the expected '
49
+ f'`{self.EXPECTED_SHAPE}` shape')
@@ -0,0 +1,56 @@
1
+ """Command line interface behind the `pr-title` executable:
2
+ `pr-title [BRANCH_NAME] [--format text|json]`."""
3
+
4
+ import argparse
5
+ import json
6
+ import subprocess
7
+ import sys
8
+
9
+ from . import __version__
10
+ from .application import Application
11
+
12
+
13
+ def _build_parser() -> argparse.ArgumentParser:
14
+ parser = argparse.ArgumentParser(
15
+ prog='pr-title',
16
+ description='Print a pull request title with labels derived from '
17
+ 'a topic branch name.')
18
+ parser.add_argument(
19
+ 'branch_name', nargs='?', metavar='BRANCH_NAME',
20
+ help='Topic branch name (default: the current git branch)')
21
+ parser.add_argument(
22
+ '--format', choices=('text', 'json'), default='text',
23
+ help='Output format (default: text)')
24
+ parser.add_argument('--version', action='version', version=__version__)
25
+ return parser
26
+
27
+
28
+ def _current_git_branch() -> str:
29
+ try:
30
+ return subprocess.run(
31
+ ['git', 'branch', '--show-current'],
32
+ capture_output=True, text=True, check=False).stdout.strip()
33
+ except OSError:
34
+ return ''
35
+
36
+
37
+ def main(argv: list[str] | None = None) -> int:
38
+ args = _build_parser().parse_args(argv)
39
+ try:
40
+ branch_name = args.branch_name or _current_git_branch()
41
+ if not branch_name:
42
+ raise ValueError('No branch name given and the current git '
43
+ 'branch could not be detected')
44
+ application = Application(branch_name=branch_name)
45
+ except ValueError as error:
46
+ print(error, file=sys.stderr)
47
+ return 1
48
+ if args.format == 'json':
49
+ print(json.dumps(application.to_dict()))
50
+ else:
51
+ print(application.title)
52
+ return 0
53
+
54
+
55
+ if __name__ == '__main__':
56
+ sys.exit(main())
File without changes
@@ -0,0 +1,112 @@
1
+ Metadata-Version: 2.4
2
+ Name: spreen-pr
3
+ Version: 0.1.0
4
+ Summary: Print a pull request title with labels derived from the topic branch name.
5
+ Author: hayat01sh1da
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/hayat01sh1da/spreen-pr
8
+ Project-URL: Source, https://github.com/hayat01sh1da/spreen-pr
9
+ Project-URL: Changelog, https://github.com/hayat01sh1da/spreen-pr/blob/master/CHANGELOG.md
10
+ Keywords: github,pull-request,title,label,branch
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Environment :: Console
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Operating System :: OS Independent
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Topic :: Software Development :: Version Control :: Git
17
+ Requires-Python: >=3.10
18
+ Description-Content-Type: text/markdown
19
+ License-File: LICENSE.txt
20
+ Dynamic: license-file
21
+
22
+ ## 1. Environment
23
+
24
+ - Python 3.14.6
25
+ - pip 26.1.2
26
+
27
+ ## 2. Installation
28
+
29
+ ```command
30
+ $ pipx install spreen-pr
31
+ ```
32
+
33
+ (`pip install spreen-pr` works too if you prefer managing the environment yourself.)
34
+
35
+ For development, install the dependencies via requirements.txt:
36
+
37
+ ```command
38
+ $ pip install -r requirements.txt
39
+ ```
40
+
41
+ ## 3. Execution
42
+
43
+ ```command
44
+ $ pr-title hayat01sh1da/issue-89/service/improve-onboarding-flow
45
+ [service] Improve Onboarding Flow
46
+ ```
47
+
48
+ With no argument, the current git branch is used:
49
+
50
+ ```command
51
+ $ git switch hayat01sh1da/issue-90/hotfix/service/fix_login_crash
52
+ $ pr-title
53
+ [Hotfix][service] Fix Login Crash
54
+ ```
55
+
56
+ `--format json` exposes the labels separately for scripting and CI:
57
+
58
+ ```command
59
+ $ pr-title hayat01sh1da/issue-90/hotfix/service/fix_login_crash --format json
60
+ {"title": "[Hotfix][service] Fix Login Crash", "labels": ["Hotfix", "service"]}
61
+ ```
62
+
63
+ As a library:
64
+
65
+ ```python
66
+ from spreen_pr import Application
67
+
68
+ Application.run(branch_name='hayat01sh1da/issue-89/service/improve-onboarding-flow')
69
+ # => '[service] Improve Onboarding Flow'
70
+
71
+ application = Application(branch_name='hayat01sh1da/issue-90/hotfix/service/fix_login_crash')
72
+ application.title # => '[Hotfix][service] Fix Login Crash'
73
+ application.labels # => ['Hotfix', 'service']
74
+ ```
75
+
76
+ ## 4. Unit Test
77
+
78
+ ```command
79
+ $ pytest
80
+ ============================= test session starts ==============================
81
+ platform linux -- Python 3.14.6, pytest-9.1.1, pluggy-1.6.0
82
+ rootdir: spreen-pr/pypi
83
+ configfile: pyproject.toml
84
+ collected 15 items
85
+
86
+ test/test_application.py ......... [ 60%]
87
+ test/test_cli.py ...... [100%]
88
+
89
+ ============================== 15 passed in 0.12s ===============================
90
+ ```
91
+
92
+ ## 5. Static Code Analysis
93
+
94
+ ```command
95
+ $ flake8 .
96
+ $ autoflake8 --in-place --remove-duplicate-keys --remove-unused-variables --recursive .
97
+ $ autopep8 --in-place --aggressive --aggressive --recursive .
98
+ ```
99
+
100
+ ## 6. Type Checks
101
+
102
+ ```command
103
+ $ mypy .
104
+ Success: no issues found in 6 source files
105
+ ```
106
+
107
+ ## 7. Build
108
+
109
+ ```command
110
+ $ python -m build
111
+ $ pipx install ./dist/spreen_pr-0.1.0-py3-none-any.whl
112
+ ```
@@ -0,0 +1,14 @@
1
+ LICENSE.txt
2
+ README.md
3
+ pyproject.toml
4
+ src/spreen_pr/__init__.py
5
+ src/spreen_pr/application.py
6
+ src/spreen_pr/cli.py
7
+ src/spreen_pr/py.typed
8
+ src/spreen_pr.egg-info/PKG-INFO
9
+ src/spreen_pr.egg-info/SOURCES.txt
10
+ src/spreen_pr.egg-info/dependency_links.txt
11
+ src/spreen_pr.egg-info/entry_points.txt
12
+ src/spreen_pr.egg-info/top_level.txt
13
+ test/test_application.py
14
+ test/test_cli.py
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ pr-title = spreen_pr.cli:main
@@ -0,0 +1 @@
1
+ spreen_pr
@@ -0,0 +1,64 @@
1
+ import pytest
2
+
3
+ from spreen_pr import Application
4
+
5
+
6
+ @pytest.mark.parametrize(
7
+ ('branch_name', 'expected'),
8
+ [
9
+ (
10
+ 'hayat01sh1da/issue-xxxxx/service/this_is_a_test_branch',
11
+ '[service] This Is A Test Branch',
12
+ ),
13
+ (
14
+ 'hayat01sh1da/issue-xxxxx/hotfix/service/this_is_a_test_branch',
15
+ '[Hotfix][service] This Is A Test Branch',
16
+ ),
17
+ (
18
+ 'hayat01sh1da/issue-xxxxx/service/this-is-a-test-branch',
19
+ '[service] This Is A Test Branch',
20
+ ),
21
+ ],
22
+ )
23
+ def test_run(branch_name: str, expected: str) -> None:
24
+ assert Application.run(branch_name=branch_name) == expected
25
+
26
+
27
+ @pytest.mark.parametrize(
28
+ ('branch_name', 'expected'),
29
+ [
30
+ (
31
+ 'hayat01sh1da/issue-xxxxx/service/this_is_a_test_branch',
32
+ ['service'],
33
+ ),
34
+ (
35
+ 'hayat01sh1da/issue-xxxxx/hotfix/service/this_is_a_test_branch',
36
+ ['Hotfix', 'service'],
37
+ ),
38
+ ],
39
+ )
40
+ def test_labels(branch_name: str, expected: list[str]) -> None:
41
+ assert Application(branch_name=branch_name).labels == expected
42
+
43
+
44
+ def test_to_dict() -> None:
45
+ branch_name = 'hayat01sh1da/issue-xxxxx/hotfix/service/this_is_a_test_branch'
46
+
47
+ assert Application(branch_name=branch_name).to_dict() == {
48
+ 'title': '[Hotfix][service] This Is A Test Branch',
49
+ 'labels': ['Hotfix', 'service'],
50
+ }
51
+
52
+
53
+ @pytest.mark.parametrize(
54
+ 'branch_name',
55
+ [
56
+ 'main',
57
+ 'fix/typo',
58
+ 'hayat01sh1da//service/this_is_a_test_branch',
59
+ '',
60
+ ],
61
+ )
62
+ def test_run_raises_on_an_unexpected_branch_name(branch_name: str) -> None:
63
+ with pytest.raises(ValueError, match='does not match the expected'):
64
+ Application.run(branch_name=branch_name)
@@ -0,0 +1,46 @@
1
+ import json
2
+
3
+ import pytest
4
+
5
+ from spreen_pr import __version__
6
+ from spreen_pr.cli import main
7
+
8
+
9
+ def test_text_format(capsys: pytest.CaptureFixture[str]) -> None:
10
+ status = main(['hayat01sh1da/issue-xxxxx/service/this_is_a_test_branch'])
11
+
12
+ assert status == 0
13
+ assert capsys.readouterr().out == '[service] This Is A Test Branch\n'
14
+
15
+
16
+ def test_json_format(capsys: pytest.CaptureFixture[str]) -> None:
17
+ status = main([
18
+ 'hayat01sh1da/issue-xxxxx/hotfix/service/this_is_a_test_branch',
19
+ '--format', 'json',
20
+ ])
21
+
22
+ assert status == 0
23
+ assert json.loads(capsys.readouterr().out) == {
24
+ 'title': '[Hotfix][service] This Is A Test Branch',
25
+ 'labels': ['Hotfix', 'service'],
26
+ }
27
+
28
+
29
+ def test_invalid_branch_name(capsys: pytest.CaptureFixture[str]) -> None:
30
+ status = main(['main'])
31
+
32
+ assert status == 1
33
+ assert 'does not match the expected' in capsys.readouterr().err
34
+
35
+
36
+ def test_invalid_format() -> None:
37
+ with pytest.raises(SystemExit):
38
+ main(['hayat01sh1da/issue-xxxxx/service/topic', '--format', 'yaml'])
39
+
40
+
41
+ def test_version(capsys: pytest.CaptureFixture[str]) -> None:
42
+ with pytest.raises(SystemExit) as excinfo:
43
+ main(['--version'])
44
+
45
+ assert excinfo.value.code == 0
46
+ assert capsys.readouterr().out == f'{__version__}\n'