py-conventional-semver 1.0.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 (23) hide show
  1. py_conventional_semver-1.0.0/.scripts/conventional-semver +7 -0
  2. py_conventional_semver-1.0.0/LICENSE +22 -0
  3. py_conventional_semver-1.0.0/PKG-INFO +177 -0
  4. py_conventional_semver-1.0.0/README.md +153 -0
  5. py_conventional_semver-1.0.0/pyproject.toml +38 -0
  6. py_conventional_semver-1.0.0/setup.cfg +4 -0
  7. py_conventional_semver-1.0.0/src/conventional_semver/ChangelogOutputGenerator.py +30 -0
  8. py_conventional_semver-1.0.0/src/conventional_semver/CommandLineProcessor.py +120 -0
  9. py_conventional_semver-1.0.0/src/conventional_semver/Configuration.py +160 -0
  10. py_conventional_semver-1.0.0/src/conventional_semver/GitAdapter.py +67 -0
  11. py_conventional_semver-1.0.0/src/conventional_semver/GitEntry.py +50 -0
  12. py_conventional_semver-1.0.0/src/conventional_semver/GitEntryParser.py +58 -0
  13. py_conventional_semver-1.0.0/src/conventional_semver/GitLogStream.py +40 -0
  14. py_conventional_semver-1.0.0/src/conventional_semver/OutputGenerator.py +18 -0
  15. py_conventional_semver-1.0.0/src/conventional_semver/SemverComponentType.py +12 -0
  16. py_conventional_semver-1.0.0/src/conventional_semver/SemverOutputGenerator.py +55 -0
  17. py_conventional_semver-1.0.0/src/conventional_semver/__init__.py +29 -0
  18. py_conventional_semver-1.0.0/src/conventional_semver/__main__.py +41 -0
  19. py_conventional_semver-1.0.0/src/py_conventional_semver.egg-info/PKG-INFO +177 -0
  20. py_conventional_semver-1.0.0/src/py_conventional_semver.egg-info/SOURCES.txt +21 -0
  21. py_conventional_semver-1.0.0/src/py_conventional_semver.egg-info/dependency_links.txt +1 -0
  22. py_conventional_semver-1.0.0/src/py_conventional_semver.egg-info/requires.txt +7 -0
  23. py_conventional_semver-1.0.0/src/py_conventional_semver.egg-info/top_level.txt +1 -0
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env python
2
+ # SPDX-FileCopyrightText: © 2026 Shaun Wilson
3
+ # SPDX-License-Identifier: MIT
4
+ ##
5
+ from subprocess import run
6
+ from sys import executable, argv
7
+ result = run([executable, "-m", "py_conventional_semver"] + argv[1:], check=False)
@@ -0,0 +1,22 @@
1
+ MIT License
2
+
3
+ © 2026 Shaun Wilson
4
+ (c) 2024 sw4k
5
+
6
+ Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ of this software and associated documentation files (the "Software"), to deal
8
+ in the Software without restriction, including without limitation the rights
9
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ copies of the Software, and to permit persons to whom the Software is
11
+ furnished to do so, subject to the following conditions:
12
+
13
+ The above copyright notice and permission notice shall be included in all
14
+ copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ SOFTWARE.
@@ -0,0 +1,177 @@
1
+ Metadata-Version: 2.4
2
+ Name: py-conventional-semver
3
+ Version: 1.0.0
4
+ Summary: ..a Python fork of sw4k's `conventional-semver` tool.
5
+ Author-email: Shaun Wilson <mrshaunwilson@msn.com>
6
+ License-Expression: MIT
7
+ Project-URL: Documentation, https://conventional-semver.readthedocs.io/
8
+ Project-URL: Homepage, https://github.com/wilson0x4d/conventional-semver
9
+ Project-URL: Repository, https://github.com/wilson0x4d/conventional-semver.git
10
+ Keywords: SEMVER,conventional-semver,conventional-commits,sw4k
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Operating System :: OS Independent
13
+ Classifier: Programming Language :: Python
14
+ Requires-Python: >=3.11
15
+ Description-Content-Type: text/markdown
16
+ License-File: LICENSE
17
+ Requires-Dist: hanaro>=1.0.0
18
+ Provides-Extra: dev
19
+ Requires-Dist: coverage; extra == "dev"
20
+ Requires-Dist: mypy; extra == "dev"
21
+ Requires-Dist: punit>=1.4.5; extra == "dev"
22
+ Requires-Dist: twine; extra == "dev"
23
+ Dynamic: license-file
24
+
25
+
26
+ **conventional-semver** is a [Conventional Commits](https://www.conventionalcommits.org/) processor designed to emit a [SEMVER](https://semver.org) as part of a build pipeline.
27
+
28
+ ## Usage
29
+
30
+ ```text
31
+ Usage:
32
+ conventional-semver [options] [repo-path]
33
+
34
+ Options:
35
+ --help print usage, then exit.
36
+ --version print version info, then exit.
37
+ --verbose enable verbose (debug) output.
38
+ --commit <hash> indicates which commit hash to start changelog from.
39
+ --tag <name> indicates which tag to start changelog from.
40
+ --changelog <file> overrides the name of the file the changelog is written to, otherwise 'changelog.md' is the default.
41
+ --no-semver disable SEMVER output to STDOUT.
42
+ --major SEMVER 'Major' component will start with this value, default is '0'.
43
+ --minor SEMVER 'Minor' component will start with this value, default is '0'.
44
+ --patch SEMVER 'Patch' component will start with this value, default is '0'.
45
+ --git-path <path> overrides the path to `git` tool, otherwise `git` must be in environment PATH.
46
+
47
+ Parameters:
48
+ repo-path the path of the git repository to process, if not specified defaults to working directory.
49
+ ```
50
+
51
+ ### Generating SEMVER
52
+
53
+ When you run **conventional-semver** from within a `git` repository, it will automatically process the log and emit a semver.
54
+
55
+ Example:
56
+
57
+ ```bash
58
+ % conventional-semver
59
+ 0.1.23
60
+ ```
61
+
62
+ This allows you to pull a semver into an Environment Variable, evaluate it as an Argument to another tool, or pipe it to a file/stream for additional processing:
63
+
64
+ ```bash
65
+ % export SEMVER=$(conventional-semver)
66
+ % echo $SEMVER
67
+ 0.1.23
68
+ ```
69
+
70
+ #### Override Baseline SEMVER
71
+
72
+ Projects adopting Conventional Commits may need to customize the baseline SEMVER, rather than starting from `0.0.0`. This can be done with the `--major`, `--minor`, and `--patch` arguments.
73
+
74
+ When run on a repo containing three non-conventional commits:
75
+
76
+ ```bash
77
+ % conventional-semver --major 1 --minor 2 --patch 3
78
+ 1.2.6
79
+ ```
80
+
81
+ #### SEMVER Configuration
82
+
83
+ When run without any command-line arguments a default set of settings are used which implement a standard Conventional Commits behavior.
84
+
85
+ To customize behavior a configuration file may be created. This file can be passed in using a `--config` argument, or, placed into one of the following well-known locations (and in the following order):
86
+
87
+ * `./conventional-semver.conf` (working directory.)
88
+ * `~/.config/conventional-semver/settings.conf` (user profile `.config` directory.)
89
+ * `/etc/conventional-semver/settings.conf` (root `/etc` directory.)
90
+
91
+ The configuration file should have the following format:
92
+
93
+ ```ini
94
+ # lines starting with hash (#) are comments
95
+ # empty lines, like the following, are ignored
96
+
97
+ # conventional commit "type" mappings are
98
+ # configured in a [types] section. the following
99
+ # mirrors the default configuration:
100
+ [types]
101
+ .*!=major
102
+ feat.*=minor
103
+ .*=patch
104
+
105
+ # conventional commit "footer" mappings are
106
+ # configured in a [footers] section. the following
107
+ # mirrors the default configuration:
108
+ [footers]
109
+ BREAKING[\-\.]CHANGE=major
110
+
111
+ # in each of the above sections, each line
112
+ # represents a key-value pair. the key is a regex
113
+ # and the value is a component type to be
114
+ # incremented if the regex is a match.
115
+ ```
116
+
117
+ There is a sample configuration file located in this repo as `config/conventional-semver.conf` which contains additional comments and explanations, you can customize it to fit your needs and then place it at one of the well-known locations mentioned above.
118
+
119
+ ## Not yet Implemented
120
+
121
+ ### Generating CHANGELOG
122
+
123
+ To generate a CHANGELOG you specify the `--changelog [filename]` switch, this takes an optional filename argument. If no filename is provided a default filename of `./CHANGELOG` is used to emit a file into the current working directory.
124
+
125
+ Example:
126
+
127
+ ```bash
128
+ % conventional-semver --changelog
129
+ ```
130
+
131
+ #### CHANGELOG Templates
132
+
133
+ The CHANGELOG output is controlled through one or more templates. This includes a required "entry" template, and optional "header" and "footer" templates. Combined this is meant to provide enough flexibility that you could emit templates in various structured formats such as XML, JSON, Markdown, etc.
134
+
135
+ The default templates are meant to produce a generic TEXT file that is markdown-friendly.
136
+
137
+ ##### CHANGELOG Entry Template
138
+
139
+ For each SEMVER increment a CHANGELOG Entry is emitted.
140
+
141
+ The format of each CHANGELOG Entry is taken from a file named `changelog-entry.template`, when `--changelog` is specified this file is required to be present or the command will fail.
142
+
143
+ This is the default content of this template:
144
+
145
+ ```text
146
+ $(DATE) $(SEMVER)-$(HASH)
147
+ $(TYPE)$(SCOPE): $(MESSAGE)
148
+ ```
149
+
150
+ The entry template supports the following expando variables:
151
+
152
+ | Expando | Comment |
153
+ |-|-|
154
+ | $(DATE) | The date of the commit which caused SEMVER increment. This is emitted in the default culture of the current environment. |
155
+ | $(SEMVER) | The calculated SEMVER value. |
156
+ | $(HASH) | The short-form git commit hash. |
157
+ | $(TYPE) | The Conventional Commits `<type>` value. |
158
+ | $(SCOPE) | If any, the Conventional Commits `[scope]` value, including parenthesis. |
159
+ | $(MESSAGE) | The commit message, sans `<type>` and `[scope]`. |
160
+ | $(BODY) | If any, the commit body. |
161
+ | $(TRAILERS) | If any, the commit trailers (footers). |
162
+
163
+ ##### CHANGELOG Header Template
164
+
165
+ The header prepended to the CHANGELOG comes from a file named `changelog-header.template`.
166
+
167
+ There are no special expando variables supported in the header.
168
+
169
+ ##### CHANGELOG Footer Template
170
+
171
+ The footer appended to the CHANGELOG comes from a file named `changelog-footer.template`.
172
+
173
+ There are no special expando variables supported in the footer.
174
+
175
+ ##### CHANGELOG Template Caveats
176
+
177
+ No escaping is performed on any of the emitted values. Thus, it is possible for commit messages to interact with parsers/renderers in unintended ways. For example if you make a template to emit an HTML changelog, and a commit message contains content which looks like an 'element', it will most likely result in a changelog that doesn't render as expected.
@@ -0,0 +1,153 @@
1
+
2
+ **conventional-semver** is a [Conventional Commits](https://www.conventionalcommits.org/) processor designed to emit a [SEMVER](https://semver.org) as part of a build pipeline.
3
+
4
+ ## Usage
5
+
6
+ ```text
7
+ Usage:
8
+ conventional-semver [options] [repo-path]
9
+
10
+ Options:
11
+ --help print usage, then exit.
12
+ --version print version info, then exit.
13
+ --verbose enable verbose (debug) output.
14
+ --commit <hash> indicates which commit hash to start changelog from.
15
+ --tag <name> indicates which tag to start changelog from.
16
+ --changelog <file> overrides the name of the file the changelog is written to, otherwise 'changelog.md' is the default.
17
+ --no-semver disable SEMVER output to STDOUT.
18
+ --major SEMVER 'Major' component will start with this value, default is '0'.
19
+ --minor SEMVER 'Minor' component will start with this value, default is '0'.
20
+ --patch SEMVER 'Patch' component will start with this value, default is '0'.
21
+ --git-path <path> overrides the path to `git` tool, otherwise `git` must be in environment PATH.
22
+
23
+ Parameters:
24
+ repo-path the path of the git repository to process, if not specified defaults to working directory.
25
+ ```
26
+
27
+ ### Generating SEMVER
28
+
29
+ When you run **conventional-semver** from within a `git` repository, it will automatically process the log and emit a semver.
30
+
31
+ Example:
32
+
33
+ ```bash
34
+ % conventional-semver
35
+ 0.1.23
36
+ ```
37
+
38
+ This allows you to pull a semver into an Environment Variable, evaluate it as an Argument to another tool, or pipe it to a file/stream for additional processing:
39
+
40
+ ```bash
41
+ % export SEMVER=$(conventional-semver)
42
+ % echo $SEMVER
43
+ 0.1.23
44
+ ```
45
+
46
+ #### Override Baseline SEMVER
47
+
48
+ Projects adopting Conventional Commits may need to customize the baseline SEMVER, rather than starting from `0.0.0`. This can be done with the `--major`, `--minor`, and `--patch` arguments.
49
+
50
+ When run on a repo containing three non-conventional commits:
51
+
52
+ ```bash
53
+ % conventional-semver --major 1 --minor 2 --patch 3
54
+ 1.2.6
55
+ ```
56
+
57
+ #### SEMVER Configuration
58
+
59
+ When run without any command-line arguments a default set of settings are used which implement a standard Conventional Commits behavior.
60
+
61
+ To customize behavior a configuration file may be created. This file can be passed in using a `--config` argument, or, placed into one of the following well-known locations (and in the following order):
62
+
63
+ * `./conventional-semver.conf` (working directory.)
64
+ * `~/.config/conventional-semver/settings.conf` (user profile `.config` directory.)
65
+ * `/etc/conventional-semver/settings.conf` (root `/etc` directory.)
66
+
67
+ The configuration file should have the following format:
68
+
69
+ ```ini
70
+ # lines starting with hash (#) are comments
71
+ # empty lines, like the following, are ignored
72
+
73
+ # conventional commit "type" mappings are
74
+ # configured in a [types] section. the following
75
+ # mirrors the default configuration:
76
+ [types]
77
+ .*!=major
78
+ feat.*=minor
79
+ .*=patch
80
+
81
+ # conventional commit "footer" mappings are
82
+ # configured in a [footers] section. the following
83
+ # mirrors the default configuration:
84
+ [footers]
85
+ BREAKING[\-\.]CHANGE=major
86
+
87
+ # in each of the above sections, each line
88
+ # represents a key-value pair. the key is a regex
89
+ # and the value is a component type to be
90
+ # incremented if the regex is a match.
91
+ ```
92
+
93
+ There is a sample configuration file located in this repo as `config/conventional-semver.conf` which contains additional comments and explanations, you can customize it to fit your needs and then place it at one of the well-known locations mentioned above.
94
+
95
+ ## Not yet Implemented
96
+
97
+ ### Generating CHANGELOG
98
+
99
+ To generate a CHANGELOG you specify the `--changelog [filename]` switch, this takes an optional filename argument. If no filename is provided a default filename of `./CHANGELOG` is used to emit a file into the current working directory.
100
+
101
+ Example:
102
+
103
+ ```bash
104
+ % conventional-semver --changelog
105
+ ```
106
+
107
+ #### CHANGELOG Templates
108
+
109
+ The CHANGELOG output is controlled through one or more templates. This includes a required "entry" template, and optional "header" and "footer" templates. Combined this is meant to provide enough flexibility that you could emit templates in various structured formats such as XML, JSON, Markdown, etc.
110
+
111
+ The default templates are meant to produce a generic TEXT file that is markdown-friendly.
112
+
113
+ ##### CHANGELOG Entry Template
114
+
115
+ For each SEMVER increment a CHANGELOG Entry is emitted.
116
+
117
+ The format of each CHANGELOG Entry is taken from a file named `changelog-entry.template`, when `--changelog` is specified this file is required to be present or the command will fail.
118
+
119
+ This is the default content of this template:
120
+
121
+ ```text
122
+ $(DATE) $(SEMVER)-$(HASH)
123
+ $(TYPE)$(SCOPE): $(MESSAGE)
124
+ ```
125
+
126
+ The entry template supports the following expando variables:
127
+
128
+ | Expando | Comment |
129
+ |-|-|
130
+ | $(DATE) | The date of the commit which caused SEMVER increment. This is emitted in the default culture of the current environment. |
131
+ | $(SEMVER) | The calculated SEMVER value. |
132
+ | $(HASH) | The short-form git commit hash. |
133
+ | $(TYPE) | The Conventional Commits `<type>` value. |
134
+ | $(SCOPE) | If any, the Conventional Commits `[scope]` value, including parenthesis. |
135
+ | $(MESSAGE) | The commit message, sans `<type>` and `[scope]`. |
136
+ | $(BODY) | If any, the commit body. |
137
+ | $(TRAILERS) | If any, the commit trailers (footers). |
138
+
139
+ ##### CHANGELOG Header Template
140
+
141
+ The header prepended to the CHANGELOG comes from a file named `changelog-header.template`.
142
+
143
+ There are no special expando variables supported in the header.
144
+
145
+ ##### CHANGELOG Footer Template
146
+
147
+ The footer appended to the CHANGELOG comes from a file named `changelog-footer.template`.
148
+
149
+ There are no special expando variables supported in the footer.
150
+
151
+ ##### CHANGELOG Template Caveats
152
+
153
+ No escaping is performed on any of the emitted values. Thus, it is possible for commit messages to interact with parsers/renderers in unintended ways. For example if you make a template to emit an HTML changelog, and a commit message contains content which looks like an 'element', it will most likely result in a changelog that doesn't render as expected.
@@ -0,0 +1,38 @@
1
+ [project]
2
+ name = "py-conventional-semver"
3
+ version = "1.0.0"
4
+ description = "..a Python fork of sw4k's `conventional-semver` tool."
5
+ keywords = ["SEMVER", "conventional-semver", "conventional-commits", "sw4k"]
6
+ authors = [
7
+ { name="Shaun Wilson", email="mrshaunwilson@msn.com" }
8
+ ]
9
+ readme = "README.md"
10
+ license = "MIT"
11
+ classifiers = [
12
+ "Intended Audience :: Developers",
13
+ "Operating System :: OS Independent",
14
+ "Programming Language :: Python"
15
+ ]
16
+ requires-python = ">=3.11"
17
+ dependencies = [
18
+ "hanaro (>=1.0.0)"
19
+ ]
20
+
21
+ [project.optional-dependencies]
22
+ dev = [
23
+ "coverage",
24
+ "mypy",
25
+ "punit (>=1.4.5)",
26
+ "twine"
27
+ ]
28
+
29
+ [project.urls]
30
+ Documentation = "https://conventional-semver.readthedocs.io/"
31
+ Homepage = "https://github.com/wilson0x4d/conventional-semver"
32
+ Repository = "https://github.com/wilson0x4d/conventional-semver.git"
33
+
34
+ [tool.setuptools.data-files]
35
+ bin = [".scripts/conventional-semver"]
36
+
37
+ [tool.setuptools.package-data]
38
+ "py-conventional-semver" = ["py.typed"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,30 @@
1
+ # SPDX-FileCopyrightText: © 2026 Shaun Wilson
2
+ # SPDX-FileCopyrightText: (c) 2024 sw4k
3
+ # SPDX-License-Identifier: MIT
4
+
5
+ from __future__ import annotations
6
+
7
+ import hanaro
8
+ import logging
9
+
10
+ from .Configuration import Configuration
11
+ from .GitEntry import GitEntry
12
+ from .OutputGenerator import OutputGenerator
13
+
14
+
15
+ class ChangelogOutputGenerator(OutputGenerator):
16
+
17
+ __config: Configuration
18
+ __logger: logging.Logger
19
+
20
+ def __init__(self, config: Configuration) -> None:
21
+ self.__config = config
22
+ self.__logger = hanaro.get_logger()
23
+
24
+ def handle_commit_entry(self, entry: GitEntry) -> None:
25
+ # TODO: implement handling logic
26
+ self.__logger.warn('TODO: HandleCommitEntry')
27
+
28
+ def generate_output(self) -> None:
29
+ # TODO: implement output generation
30
+ self.__logger.warn('TODO: GenerateOutput')
@@ -0,0 +1,120 @@
1
+ # SPDX-FileCopyrightText: © 2026 Shaun Wilson
2
+ # SPDX-FileCopyrightText: (c) 2024 sw4k
3
+ # SPDX-License-Identifier: MIT
4
+
5
+ from __future__ import annotations
6
+
7
+ import sys
8
+ import hanaro
9
+ import logging
10
+
11
+ from .Configuration import Configuration
12
+
13
+
14
+ class CommandlineProcessor:
15
+
16
+ __config: Configuration
17
+ __logger: logging.Logger
18
+ __paths: list[str]
19
+
20
+ def __init__(self, config: Configuration, version: str, commit: str) -> None:
21
+ self.__commit = commit
22
+ self.__config = config
23
+ self.__logger = hanaro.get_logger()
24
+ self.__paths = []
25
+ self.__version = version
26
+
27
+ def __print_info(self) -> None:
28
+ print(f'conventional-semver {self.__version} ({self.__commit})')
29
+
30
+ def __print_usage(self) -> None:
31
+ print()
32
+ print('Usage:')
33
+ print('\conventional-semver [options] [repo-path]')
34
+ print()
35
+ print('Options:')
36
+ print('\t--help print usage, then exit.')
37
+ print('\t--version print version info, then exit.')
38
+ print('\t--verbose enable verbose (debug) output.')
39
+ print('\t--commit <hash> indicates which commit hash to start changelog from.')
40
+ print('\t--tag <name> indicates which tag to start changelog from.')
41
+ print('\t--changelog <file> overrides the name of the file the changelog is written to, otherwise \'changelog.md\' is the default.')
42
+ print('\t--no-semver disable SEMVER output to STDOUT.')
43
+ print('\t--major SEMVER \'Major\' component will start with this value, default is \'0\'.')
44
+ print('\t--minor SEMVER \'Minor\' component will start with this value, default is \'0\'.')
45
+ print('\t--patch SEMVER \'Patch\' component will start with this value, default is \'0\'.')
46
+ print('\t--git-path <path> overrides the path to `git` tool, otherwise `git` must be in environment PATH.')
47
+ print()
48
+ print('Parameters:')
49
+ print('\trepo-path the path of the git repository to process, if not specified defaults to working directory.')
50
+ print()
51
+
52
+ def process_command_line(self, argv: list[str] | None = None) -> None:
53
+ if argv is None:
54
+ argv = sys.argv
55
+ args = argv[1:]
56
+ i = 0
57
+ while i < len(args):
58
+ arg = args[i]
59
+ if arg == '--help':
60
+ self.__print_info()
61
+ self.__print_usage()
62
+ sys.exit(0)
63
+ elif arg == '--version':
64
+ self.__print_info()
65
+ sys.exit(0)
66
+ elif arg == '--no-semver':
67
+ self.__config.disable_semver_output = True
68
+ elif arg == '--changelog':
69
+ i += 1
70
+ if i >= len(args) or args[i].startswith('-'):
71
+ raise IndexError('missing argument to `' + arg + '` option')
72
+ self.__config.changelog_output_file = args[i]
73
+ elif arg == '--commit':
74
+ if getattr(self.__config, 'start_tag', ''):
75
+ raise RuntimeError('cannot specify both `--commit` and `--tag` options.')
76
+ i += 1
77
+ if i >= len(args) or args[i].startswith('-'):
78
+ raise IndexError('missing argument to `' + arg + '` option')
79
+ self.__config.start_commit_hash = args[i]
80
+ elif arg == '--config':
81
+ i += 1
82
+ if i >= len(args) or args[i].startswith('-'):
83
+ raise IndexError('missing argument to `' + arg + '` option')
84
+ self.__config.config_file = args[i]
85
+ elif arg == '--git-path':
86
+ i += 1
87
+ if i >= len(args) or args[i].startswith('-'):
88
+ raise IndexError('missing argument to `' + arg + '` option')
89
+ self.__config.git_path = args[i]
90
+ elif arg == '--major':
91
+ i += 1
92
+ if i >= len(args) or args[i].startswith('-'):
93
+ raise IndexError('missing argument to `' + arg + '` option')
94
+ self.__config.major_start = int(args[i])
95
+ elif arg == '--minor':
96
+ i += 1
97
+ if i >= len(args) or args[i].startswith('-'):
98
+ raise IndexError('missing argument to `' + arg + '` option')
99
+ self.__config.minor_start = int(args[i])
100
+ elif arg == '--patch':
101
+ i += 1
102
+ if i >= len(args) or args[i].startswith('-'):
103
+ raise IndexError('missing argument to `' + arg + '` option')
104
+ self.__config.patch_start = int(args[i])
105
+ elif arg == '--tag':
106
+ if getattr(self.__config, 'start_commit_hash', ''):
107
+ raise RuntimeError('cannot specify both `--commit` and `--tag` options.')
108
+ i += 1
109
+ if i >= len(args) or args[i].startswith('-'):
110
+ raise IndexError('missing argument to `' + arg + '` option')
111
+ self.__config.start_tag = args[i]
112
+ elif arg == '--verbose':
113
+ # TODO
114
+ pass
115
+ elif not self.__config.repo_path and not arg.startswith('-'):
116
+ self.__config.repo_path = arg
117
+ else:
118
+ self.__print_usage()
119
+ raise ValueError('unexpected argument: ' + arg)
120
+ i += 1
@@ -0,0 +1,160 @@
1
+ # SPDX-FileCopyrightText: © 2026 Shaun Wilson
2
+ # SPDX-FileCopyrightText: (c) 2024 sw4k
3
+ # SPDX-License-Identifier: MIT
4
+
5
+ from __future__ import annotations
6
+
7
+ import hanaro
8
+ import logging
9
+ import os
10
+ from pathlib import Path
11
+ import re
12
+
13
+ from .SemverComponentType import SemverComponentType
14
+
15
+
16
+ class Configuration:
17
+
18
+ changelog_output_file: str
19
+ config_file: str
20
+ disable_semver_output: bool
21
+ footers: list[tuple[re.Pattern, SemverComponentType]]
22
+ git_path: str
23
+ major_start: int
24
+ minor_start: int
25
+ patch_start: int
26
+ start_commit_hash: str
27
+ start_tag: str
28
+ types: list[tuple[re.Pattern, SemverComponentType]]
29
+ repo_path: str
30
+
31
+ __logger: logging.Logger
32
+
33
+ def __init__(self) -> None:
34
+ self.__logger = hanaro.get_logger()
35
+
36
+ self.changelog_output_file = ''
37
+ self.config_file = ''
38
+ self.disable_semver_output = False
39
+ self.footers = []
40
+ self.git_path = ''
41
+ self.major_start = 0
42
+ self.minor_start = 0
43
+ self.patch_start = 0
44
+ self.start_commit_hash = ''
45
+ self.start_tag = ''
46
+ self.types = []
47
+ self.repo_path = ''
48
+
49
+ def __build_footer_regex(self, input_str: str) -> re.Pattern:
50
+ pattern = r'[\S\s]*' + input_str + r'[\S\s]*'
51
+ return re.compile(pattern, re.MULTILINE | re.IGNORECASE)
52
+
53
+ def __build_type_regex(self, input_str: str) -> re.Pattern:
54
+ pattern = r'^' + input_str + r':[\S\s]*'
55
+ return re.compile(pattern, re.MULTILINE | re.IGNORECASE)
56
+
57
+ def __create_entry(self, entry: str, entry_code: str) -> bool:
58
+ idx = entry.find('=')
59
+ if idx == -1:
60
+ return False
61
+
62
+ regex = self.__build_type_regex(entry[:idx].strip())
63
+
64
+ value_part = entry[idx + 1 :].strip()
65
+ if not value_part:
66
+ return False
67
+ component_code = value_part[0].lower()
68
+
69
+ component_type = None
70
+ if component_code == 'j':
71
+ component_type = SemverComponentType.MAJOR
72
+ elif component_code == 'n':
73
+ component_type = SemverComponentType.MINOR
74
+ elif component_code == 't':
75
+ component_type = SemverComponentType.PATCH
76
+ else:
77
+ return False
78
+
79
+ if entry_code == 't':
80
+ self.types.append((regex, component_type))
81
+ elif entry_code == 'f':
82
+ self.footers.append((regex, component_type))
83
+ else:
84
+ return False
85
+
86
+ return True
87
+
88
+ def __process_configuration_file(self, path: str) -> bool:
89
+ config_path = Path(path).expanduser()
90
+ if not config_path.is_file():
91
+ return False
92
+
93
+ entry_type = '?' # tracks whether we are inside [types] or [footers]
94
+
95
+ with config_path.open('r', encoding='utf-8') as stream:
96
+ for raw_line in stream:
97
+ line = raw_line.rstrip('\n')
98
+ if not line or line.startswith('#'):
99
+ continue
100
+ if line == '[types]':
101
+ entry_type = 't'
102
+ elif line == '[footers]':
103
+ entry_type = 'f'
104
+ else:
105
+ if not self.__create_entry(line, entry_type):
106
+ self.__logger.warn('Could not configure entry from: ' + line)
107
+ return True
108
+
109
+ def __print_configuration_summary(self) -> None:
110
+ # TODO:
111
+ pass
112
+
113
+ def process_configuration(self) -> None:
114
+ config_processed = False
115
+
116
+ if self.config_file:
117
+ config_processed = self.__process_configuration_file(self.config_file)
118
+ if not config_processed:
119
+ raise RuntimeError(
120
+ f'Failed to process expected configuration file: {self.config_file}'
121
+ )
122
+ else:
123
+ candidate_paths = [
124
+ './conventional-semver.json',
125
+ os.path.expanduser('~/.config/conventional-semver/settings.json'),
126
+ '/etc/conventional-semver/settings.json',
127
+ ]
128
+ for cand in candidate_paths:
129
+ if self.__process_configuration_file(cand):
130
+ config_processed = True
131
+ break
132
+
133
+ if not config_processed:
134
+ # apply a default set of regexes
135
+ self.types.append(
136
+ (self.__build_type_regex('.*!'), SemverComponentType.MAJOR)
137
+ )
138
+ self.types.append(
139
+ (self.__build_type_regex('feat.*'), SemverComponentType.MINOR)
140
+ )
141
+ self.types.append(
142
+ (self.__build_type_regex('.*'), SemverComponentType.PATCH)
143
+ )
144
+ self.footers.append(
145
+ (
146
+ self.__build_footer_regex(r'BREAKING[\-\.]CHANGE'),
147
+ SemverComponentType.MAJOR,
148
+ )
149
+ )
150
+
151
+ if not self.repo_path:
152
+ self.repo_path = str(Path.cwd())
153
+
154
+ if (self.start_commit_hash or self.start_tag) and not self.changelog_output_file:
155
+ self.changelog_output_file = str(Path.cwd() / 'changelog.md')
156
+
157
+ if not self.git_path:
158
+ self.git_path = 'git'
159
+
160
+ self.__print_configuration_summary()
@@ -0,0 +1,67 @@
1
+ # git_adapter.py (only the relevant parts are shown)
2
+
3
+ import hanaro
4
+ import logging
5
+ import os
6
+ import subprocess
7
+ import sys
8
+
9
+ from .Configuration import Configuration
10
+ from .GitEntryParser import GitEntryParser
11
+ from .GitLogStream import GitLogStream
12
+ from .OutputGenerator import OutputGenerator
13
+
14
+
15
+ class GitAdapter:
16
+
17
+ __config: Configuration
18
+ __git_log_stream: GitLogStream
19
+ __logger: logging.Logger
20
+ __output_generators: list[OutputGenerator]
21
+
22
+ def __init__(
23
+ self,
24
+ config: Configuration,
25
+ git_entry_parser: GitEntryParser,
26
+ output_generators: list[OutputGenerator],
27
+ ) -> None:
28
+ self.__config = config
29
+ self.__logger = hanaro.get_logger()
30
+ self.__git_log_stream = GitLogStream(git_entry_parser)
31
+ self.__output_generators = list(output_generators)
32
+
33
+ def process_git_log(self) -> None:
34
+ argv = [
35
+ self.__config.git_path,
36
+ '--no-pager',
37
+ '-C',
38
+ self.__config.repo_path,
39
+ 'log',
40
+ '--reverse',
41
+ '--format=tformat:%H%n%D%n%B%n%xef',
42
+ ]
43
+ try:
44
+ proc = subprocess.Popen(
45
+ argv,
46
+ stdout=subprocess.PIPE,
47
+ stderr=subprocess.PIPE,
48
+ cwd=self.__config.repo_path,
49
+ )
50
+ except OSError as ex:
51
+ raise RuntimeError(f'Failed to start git: {ex}') from ex
52
+
53
+ stdout_bytes, stderr_bytes = proc.communicate()
54
+
55
+ if stderr_bytes:
56
+ os.write(sys.stderr.fileno(), stderr_bytes)
57
+
58
+ self.__git_log_stream.write(stdout_bytes, len(stdout_bytes))
59
+
60
+ entry = self.__git_log_stream.readentry()
61
+ while not entry.is_empty():
62
+ for generator in self.__output_generators:
63
+ generator.handle_commit_entry(entry)
64
+ entry = self.__git_log_stream.readentry()
65
+
66
+ if proc.returncode != 0:
67
+ self.__logger.error(f'git process exited with non-zero code {proc.returncode}')
@@ -0,0 +1,50 @@
1
+ # SPDX-FileCopyrightText: © 2026 Shaun Wilson
2
+ # SPDX-FileCopyrightText: (c) 2024 sw4k
3
+ # SPDX-License-Identifier: MIT
4
+
5
+ from __future__ import annotations
6
+ from typing import Optional
7
+
8
+
9
+ class GitEntry:
10
+
11
+ def __init__(
12
+ self,
13
+ commit_hash: str = '',
14
+ body: str = '',
15
+ footers: Optional[list[str]] = None,
16
+ refs: Optional[list[str]] = None,
17
+ subject: str = '',
18
+ ) -> None:
19
+ self.commit_hash = commit_hash
20
+ self.body = body
21
+ self.footers = footers if footers is not None else []
22
+ self.refs = refs if refs is not None else []
23
+ self.subject = subject
24
+
25
+ def is_empty(self) -> bool:
26
+ """Return ``True`` when the entry contains no data."""
27
+ return (
28
+ not self.commit_hash
29
+ and not self.body
30
+ and not self.footers
31
+ and not self.refs
32
+ and not self.subject
33
+ )
34
+
35
+ def __str__(self) -> str:
36
+ """Produce a string representation of the git entry."""
37
+ if self.is_empty():
38
+ return ''
39
+
40
+ result = self.commit_hash + '\n'
41
+
42
+ if self.refs:
43
+ result += ', '.join(self.refs)
44
+
45
+ result += '\n' + self.subject + '\n\n' + self.body + '\n'
46
+
47
+ if self.footers:
48
+ result += ','.join(self.footers)
49
+
50
+ return result
@@ -0,0 +1,58 @@
1
+ # SPDX-FileCopyrightText: © 2026 Shaun Wilson
2
+ # SPDX-FileCopyrightText: (c) 2024 sw4k
3
+ # SPDX-License-Identifier: MIT
4
+
5
+ from __future__ import annotations
6
+
7
+ from .GitEntry import GitEntry
8
+
9
+
10
+ class GitEntryParser:
11
+
12
+ def parse(self, input_str: str) -> GitEntry:
13
+ if not input_str:
14
+ return GitEntry()
15
+
16
+ entry = GitEntry()
17
+
18
+ lines = input_str.split('\n')
19
+
20
+ entry.commit_hash = lines[0]
21
+
22
+ refs_raw = lines[1]
23
+ if refs_raw:
24
+ entry.refs = [
25
+ ref.strip()
26
+ for ref in refs_raw.split(',')
27
+ if ref
28
+ ]
29
+
30
+ try:
31
+ subject_end = lines.index('', 2)
32
+ except ValueError:
33
+ subject_end = len(lines)
34
+
35
+ subject_lines = lines[2:subject_end]
36
+ entry.subject = '\n'.join(subject_lines)
37
+
38
+ body_start = subject_end + 1
39
+ if body_start < len(lines):
40
+ try:
41
+ body_end = lines.index('', body_start)
42
+ except ValueError:
43
+ body_end = len(lines)
44
+
45
+ body_lines = lines[body_start:body_end]
46
+ entry.body = '\n'.join(body_lines)
47
+
48
+ footer_start = body_end + 1
49
+ if footer_start < len(lines):
50
+ entry.footers = [
51
+ footer
52
+ for footer in lines[footer_start:]
53
+ if footer
54
+ ]
55
+ else:
56
+ entry.footers = []
57
+
58
+ return entry
@@ -0,0 +1,40 @@
1
+ # SPDX-FileCopyrightText: © 2026 Shaun Wilson
2
+ # SPDX-FileCopyrightText: (c) 2024 sw4k
3
+ # SPDX-License-Identifier: MIT
4
+
5
+ from __future__ import annotations
6
+
7
+ from .GitEntry import GitEntry
8
+ from .GitEntryParser import GitEntryParser
9
+
10
+
11
+ class GitLogStream:
12
+
13
+ __git_entry_parser: GitEntryParser
14
+ __buffer: bytearray
15
+
16
+ def __init__(
17
+ self,
18
+ git_entry_parser: GitEntryParser
19
+ ) -> None:
20
+ self.__buffer = bytearray()
21
+ self.__git_entry_parser = git_entry_parser
22
+
23
+ def write(self, buf: bytes, count: int) -> None:
24
+ self.__buffer.extend(buf[:count])
25
+
26
+ def readentry(self) -> GitEntry:
27
+ terminator = 0xEF
28
+ try:
29
+ term_index = self.__buffer.index(terminator)
30
+ except ValueError:
31
+ return GitEntry()
32
+
33
+ entry_bytes = self.__buffer[:term_index]
34
+ del self.__buffer[: term_index + 2]
35
+
36
+ try:
37
+ entry_str = entry_bytes.decode('utf-8')
38
+ except UnicodeDecodeError:
39
+ entry_str = entry_bytes.decode('utf-8', errors='replace')
40
+ return self.__git_entry_parser.parse(entry_str.strip())
@@ -0,0 +1,18 @@
1
+ # SPDX-FileCopyrightText: © 2026 Shaun Wilson
2
+ # SPDX-FileCopyrightText: (c) 2024 sw4k
3
+ # SPDX-License-Identifier: MIT
4
+
5
+ from __future__ import annotations
6
+ from typing import Protocol, runtime_checkable
7
+
8
+ from .GitEntry import GitEntry
9
+
10
+
11
+ @runtime_checkable
12
+ class OutputGenerator(Protocol):
13
+
14
+ def handle_commit_entry(self, entry: GitEntry) -> None:
15
+ ...
16
+
17
+ def generate_output(self) -> None:
18
+ ...
@@ -0,0 +1,12 @@
1
+ # SPDX-FileCopyrightText: © 2026 Shaun Wilson
2
+ # SPDX-FileCopyrightText: (c) 2024 sw4k
3
+ # SPDX-License-Identifier: MIT
4
+
5
+ from enum import IntEnum
6
+
7
+
8
+ class SemverComponentType(IntEnum):
9
+ NONE = 0
10
+ PATCH = 1
11
+ MINOR = 2
12
+ MAJOR = 3
@@ -0,0 +1,55 @@
1
+ # SPDX-FileCopyrightText: © 2026 Shaun Wilson
2
+ # SPDX-FileCopyrightText: (c) 2024 sw4k
3
+ # SPDX-License-Identifier: MIT
4
+
5
+ from __future__ import annotations
6
+
7
+ from .Configuration import Configuration
8
+ from .GitEntry import GitEntry
9
+ from .OutputGenerator import OutputGenerator
10
+ from .SemverComponentType import SemverComponentType
11
+
12
+
13
+ class SemverOutputGenerator(OutputGenerator):
14
+
15
+ __config: Configuration
16
+ __major: int
17
+ __minor: int
18
+ __patch: int
19
+
20
+ def __init__(self, config: Configuration) -> None:
21
+ self.__config = config
22
+ self.__major = config.major_start
23
+ self.__minor = config.minor_start
24
+ self.__patch = config.patch_start
25
+
26
+ def handle_commit_entry(self, entry: GitEntry) -> None:
27
+ if entry.is_empty():
28
+ return
29
+
30
+ semver_component = SemverComponentType.NONE
31
+
32
+ for regex, comp in self.__config.types:
33
+ if comp.value > semver_component.value:
34
+ if regex.search(entry.subject):
35
+ semver_component = comp
36
+
37
+ for regex, comp in self.__config.footers:
38
+ if comp.value > semver_component.value:
39
+ for footer in entry.footers:
40
+ if regex.search(footer):
41
+ semver_component = comp
42
+ break
43
+
44
+ if semver_component == SemverComponentType.MAJOR:
45
+ self.__major += 1
46
+ self.__minor = 0
47
+ self.__patch = 0
48
+ elif semver_component == SemverComponentType.MINOR:
49
+ self.__minor += 1
50
+ self.__patch = 0
51
+ elif semver_component == SemverComponentType.PATCH:
52
+ self.__patch += 1
53
+
54
+ def generate_output(self) -> None:
55
+ print(f'{self.__major}.{self.__minor}.{self.__patch}')
@@ -0,0 +1,29 @@
1
+ # SPDX-FileCopyrightText: © 2026 Shaun Wilson
2
+ # SPDX-License-Identifier: MIT
3
+
4
+ from .ChangelogOutputGenerator import ChangelogOutputGenerator
5
+ from .CommandLineProcessor import CommandlineProcessor
6
+ from .Configuration import Configuration
7
+ from .GitAdapter import GitAdapter
8
+ from .GitEntry import GitEntry
9
+ from .GitEntryParser import GitEntryParser
10
+ from .GitLogStream import GitLogStream
11
+ from .OutputGenerator import OutputGenerator
12
+ from .SemverOutputGenerator import SemverOutputGenerator
13
+ from .SemverComponentType import SemverComponentType
14
+
15
+ __version__ = '1.0.0'
16
+ __commit__ = '11d7129'
17
+ __all__ = [
18
+ '__version__', '__commit__',
19
+ 'ChangelogOutputGenerator',
20
+ 'CommandlineProcessor',
21
+ 'Configuration',
22
+ 'GitAdapter',
23
+ 'GitEntry',
24
+ 'GitEntryParser',
25
+ 'GitLogStream',
26
+ 'OutputGenerator',
27
+ 'SemverOutputGenerator',
28
+ 'SemverComponentType',
29
+ ]
@@ -0,0 +1,41 @@
1
+ # SPDX-FileCopyrightText: © 2026 Shaun Wilson
2
+ # SPDX-FileCopyrightText: (c) 2024 sw4k
3
+ # SPDX-License-Identifier: MIT
4
+
5
+ from __future__ import annotations
6
+ import sys
7
+
8
+ from .Configuration import Configuration
9
+ from .CommandLineProcessor import CommandlineProcessor
10
+ from .ChangelogOutputGenerator import ChangelogOutputGenerator
11
+ from .GitEntryParser import GitEntryParser
12
+ from .GitAdapter import GitAdapter
13
+ from .OutputGenerator import OutputGenerator
14
+ from .SemverOutputGenerator import SemverOutputGenerator
15
+ from . import __version__, __commit__
16
+
17
+
18
+ def main(argv: list[str]) -> int:
19
+ config = Configuration()
20
+ cmd_processor = CommandlineProcessor(config, __version__, __commit__)
21
+ cmd_processor.process_command_line(argv)
22
+ config.process_configuration()
23
+ output_generators = list[OutputGenerator]()
24
+ if not config.disable_semver_output:
25
+ output_generators.append(SemverOutputGenerator(config))
26
+ if config.changelog_output_file:
27
+ output_generators.append(ChangelogOutputGenerator(config))
28
+ git_entry_parser = GitEntryParser()
29
+ git_adapter = GitAdapter(
30
+ config,
31
+ git_entry_parser,
32
+ output_generators,
33
+ )
34
+ git_adapter.process_git_log()
35
+ for generator in output_generators:
36
+ generator.generate_output()
37
+ return 0
38
+
39
+
40
+ if __name__ == '__main__':
41
+ sys.exit(main(sys.argv))
@@ -0,0 +1,177 @@
1
+ Metadata-Version: 2.4
2
+ Name: py-conventional-semver
3
+ Version: 1.0.0
4
+ Summary: ..a Python fork of sw4k's `conventional-semver` tool.
5
+ Author-email: Shaun Wilson <mrshaunwilson@msn.com>
6
+ License-Expression: MIT
7
+ Project-URL: Documentation, https://conventional-semver.readthedocs.io/
8
+ Project-URL: Homepage, https://github.com/wilson0x4d/conventional-semver
9
+ Project-URL: Repository, https://github.com/wilson0x4d/conventional-semver.git
10
+ Keywords: SEMVER,conventional-semver,conventional-commits,sw4k
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Operating System :: OS Independent
13
+ Classifier: Programming Language :: Python
14
+ Requires-Python: >=3.11
15
+ Description-Content-Type: text/markdown
16
+ License-File: LICENSE
17
+ Requires-Dist: hanaro>=1.0.0
18
+ Provides-Extra: dev
19
+ Requires-Dist: coverage; extra == "dev"
20
+ Requires-Dist: mypy; extra == "dev"
21
+ Requires-Dist: punit>=1.4.5; extra == "dev"
22
+ Requires-Dist: twine; extra == "dev"
23
+ Dynamic: license-file
24
+
25
+
26
+ **conventional-semver** is a [Conventional Commits](https://www.conventionalcommits.org/) processor designed to emit a [SEMVER](https://semver.org) as part of a build pipeline.
27
+
28
+ ## Usage
29
+
30
+ ```text
31
+ Usage:
32
+ conventional-semver [options] [repo-path]
33
+
34
+ Options:
35
+ --help print usage, then exit.
36
+ --version print version info, then exit.
37
+ --verbose enable verbose (debug) output.
38
+ --commit <hash> indicates which commit hash to start changelog from.
39
+ --tag <name> indicates which tag to start changelog from.
40
+ --changelog <file> overrides the name of the file the changelog is written to, otherwise 'changelog.md' is the default.
41
+ --no-semver disable SEMVER output to STDOUT.
42
+ --major SEMVER 'Major' component will start with this value, default is '0'.
43
+ --minor SEMVER 'Minor' component will start with this value, default is '0'.
44
+ --patch SEMVER 'Patch' component will start with this value, default is '0'.
45
+ --git-path <path> overrides the path to `git` tool, otherwise `git` must be in environment PATH.
46
+
47
+ Parameters:
48
+ repo-path the path of the git repository to process, if not specified defaults to working directory.
49
+ ```
50
+
51
+ ### Generating SEMVER
52
+
53
+ When you run **conventional-semver** from within a `git` repository, it will automatically process the log and emit a semver.
54
+
55
+ Example:
56
+
57
+ ```bash
58
+ % conventional-semver
59
+ 0.1.23
60
+ ```
61
+
62
+ This allows you to pull a semver into an Environment Variable, evaluate it as an Argument to another tool, or pipe it to a file/stream for additional processing:
63
+
64
+ ```bash
65
+ % export SEMVER=$(conventional-semver)
66
+ % echo $SEMVER
67
+ 0.1.23
68
+ ```
69
+
70
+ #### Override Baseline SEMVER
71
+
72
+ Projects adopting Conventional Commits may need to customize the baseline SEMVER, rather than starting from `0.0.0`. This can be done with the `--major`, `--minor`, and `--patch` arguments.
73
+
74
+ When run on a repo containing three non-conventional commits:
75
+
76
+ ```bash
77
+ % conventional-semver --major 1 --minor 2 --patch 3
78
+ 1.2.6
79
+ ```
80
+
81
+ #### SEMVER Configuration
82
+
83
+ When run without any command-line arguments a default set of settings are used which implement a standard Conventional Commits behavior.
84
+
85
+ To customize behavior a configuration file may be created. This file can be passed in using a `--config` argument, or, placed into one of the following well-known locations (and in the following order):
86
+
87
+ * `./conventional-semver.conf` (working directory.)
88
+ * `~/.config/conventional-semver/settings.conf` (user profile `.config` directory.)
89
+ * `/etc/conventional-semver/settings.conf` (root `/etc` directory.)
90
+
91
+ The configuration file should have the following format:
92
+
93
+ ```ini
94
+ # lines starting with hash (#) are comments
95
+ # empty lines, like the following, are ignored
96
+
97
+ # conventional commit "type" mappings are
98
+ # configured in a [types] section. the following
99
+ # mirrors the default configuration:
100
+ [types]
101
+ .*!=major
102
+ feat.*=minor
103
+ .*=patch
104
+
105
+ # conventional commit "footer" mappings are
106
+ # configured in a [footers] section. the following
107
+ # mirrors the default configuration:
108
+ [footers]
109
+ BREAKING[\-\.]CHANGE=major
110
+
111
+ # in each of the above sections, each line
112
+ # represents a key-value pair. the key is a regex
113
+ # and the value is a component type to be
114
+ # incremented if the regex is a match.
115
+ ```
116
+
117
+ There is a sample configuration file located in this repo as `config/conventional-semver.conf` which contains additional comments and explanations, you can customize it to fit your needs and then place it at one of the well-known locations mentioned above.
118
+
119
+ ## Not yet Implemented
120
+
121
+ ### Generating CHANGELOG
122
+
123
+ To generate a CHANGELOG you specify the `--changelog [filename]` switch, this takes an optional filename argument. If no filename is provided a default filename of `./CHANGELOG` is used to emit a file into the current working directory.
124
+
125
+ Example:
126
+
127
+ ```bash
128
+ % conventional-semver --changelog
129
+ ```
130
+
131
+ #### CHANGELOG Templates
132
+
133
+ The CHANGELOG output is controlled through one or more templates. This includes a required "entry" template, and optional "header" and "footer" templates. Combined this is meant to provide enough flexibility that you could emit templates in various structured formats such as XML, JSON, Markdown, etc.
134
+
135
+ The default templates are meant to produce a generic TEXT file that is markdown-friendly.
136
+
137
+ ##### CHANGELOG Entry Template
138
+
139
+ For each SEMVER increment a CHANGELOG Entry is emitted.
140
+
141
+ The format of each CHANGELOG Entry is taken from a file named `changelog-entry.template`, when `--changelog` is specified this file is required to be present or the command will fail.
142
+
143
+ This is the default content of this template:
144
+
145
+ ```text
146
+ $(DATE) $(SEMVER)-$(HASH)
147
+ $(TYPE)$(SCOPE): $(MESSAGE)
148
+ ```
149
+
150
+ The entry template supports the following expando variables:
151
+
152
+ | Expando | Comment |
153
+ |-|-|
154
+ | $(DATE) | The date of the commit which caused SEMVER increment. This is emitted in the default culture of the current environment. |
155
+ | $(SEMVER) | The calculated SEMVER value. |
156
+ | $(HASH) | The short-form git commit hash. |
157
+ | $(TYPE) | The Conventional Commits `<type>` value. |
158
+ | $(SCOPE) | If any, the Conventional Commits `[scope]` value, including parenthesis. |
159
+ | $(MESSAGE) | The commit message, sans `<type>` and `[scope]`. |
160
+ | $(BODY) | If any, the commit body. |
161
+ | $(TRAILERS) | If any, the commit trailers (footers). |
162
+
163
+ ##### CHANGELOG Header Template
164
+
165
+ The header prepended to the CHANGELOG comes from a file named `changelog-header.template`.
166
+
167
+ There are no special expando variables supported in the header.
168
+
169
+ ##### CHANGELOG Footer Template
170
+
171
+ The footer appended to the CHANGELOG comes from a file named `changelog-footer.template`.
172
+
173
+ There are no special expando variables supported in the footer.
174
+
175
+ ##### CHANGELOG Template Caveats
176
+
177
+ No escaping is performed on any of the emitted values. Thus, it is possible for commit messages to interact with parsers/renderers in unintended ways. For example if you make a template to emit an HTML changelog, and a commit message contains content which looks like an 'element', it will most likely result in a changelog that doesn't render as expected.
@@ -0,0 +1,21 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ .scripts/conventional-semver
5
+ src/conventional_semver/ChangelogOutputGenerator.py
6
+ src/conventional_semver/CommandLineProcessor.py
7
+ src/conventional_semver/Configuration.py
8
+ src/conventional_semver/GitAdapter.py
9
+ src/conventional_semver/GitEntry.py
10
+ src/conventional_semver/GitEntryParser.py
11
+ src/conventional_semver/GitLogStream.py
12
+ src/conventional_semver/OutputGenerator.py
13
+ src/conventional_semver/SemverComponentType.py
14
+ src/conventional_semver/SemverOutputGenerator.py
15
+ src/conventional_semver/__init__.py
16
+ src/conventional_semver/__main__.py
17
+ src/py_conventional_semver.egg-info/PKG-INFO
18
+ src/py_conventional_semver.egg-info/SOURCES.txt
19
+ src/py_conventional_semver.egg-info/dependency_links.txt
20
+ src/py_conventional_semver.egg-info/requires.txt
21
+ src/py_conventional_semver.egg-info/top_level.txt
@@ -0,0 +1,7 @@
1
+ hanaro>=1.0.0
2
+
3
+ [dev]
4
+ coverage
5
+ mypy
6
+ punit>=1.4.5
7
+ twine