dev #4
@@ -1,2 +1,3 @@
|
||||
eclipse.preferences.version=1
|
||||
encoding//src/pychangelogfactory/changelogfactory.py=utf-8
|
||||
encoding/<project>=UTF-8
|
||||
|
||||
@@ -42,30 +42,170 @@ From master git repository:
|
||||
|
||||
### Sample code
|
||||
``` py
|
||||
from pychangelogfactory import ChangeLogFormater
|
||||
from pychangelogfactory import ChangeLogFactory
|
||||
|
||||
raw_changelog='''
|
||||
feat: add a nice feature to the project
|
||||
style: reindent the full Foo class
|
||||
security: fix a security leak on the Foo2 component
|
||||
'''
|
||||
ChangeLogFormater.FactoryProcessFullChangelog(raw_changelog)
|
||||
changelog = ChangeLogFormater.RenderFullChangelog()
|
||||
raw_changelog = (
|
||||
"feat: add a nice feature to the project\n"
|
||||
"style: reindent the full Foo class\n"
|
||||
"security: fix a security issue on the Foo2 component\n"
|
||||
"security: fix another security problem on the Foo2 component\n"
|
||||
"improve core performances by reducing complexity\n"
|
||||
"some random changes in the text content\n"
|
||||
)
|
||||
hdlr = ChangeLogFactory()
|
||||
hdlr.ProcessFullChangelog(self.raw_changelog)
|
||||
changelog = hdlr.RenderFullChangelog()
|
||||
print(changelog)
|
||||
```
|
||||
|
||||
#### Or shorted version:
|
||||
|
||||
``` py
|
||||
hdlr = ChangeLogFactory(self.raw_changelog)
|
||||
changelog = hdlr.RenderFullChangelog()
|
||||
```
|
||||
#### Or one-liner version:
|
||||
|
||||
``` py
|
||||
changelog = ChangeLogFactory(self.raw_changelog).RenderFullChangelog()
|
||||
```
|
||||
|
||||
### Output(Raw)
|
||||
|
||||
#### Features :sparkles::
|
||||
#### Features :sparkles: :
|
||||
> add a nice feature to the project
|
||||
#### Security :shield::
|
||||
> fix a security leak on the Foo2 component
|
||||
#### Style :art::
|
||||
#### Security :shield: :
|
||||
> security: fix a security issue on the Foo2 component
|
||||
> security: fix another security problem on the Foo2 component
|
||||
#### Performance Enhancements :rocket: :
|
||||
> improve core performances by reducing complexity
|
||||
#### Style :art: :
|
||||
> reindent the full Foo class
|
||||
|
||||
### Output (rendered)
|
||||
#### Features :sparkles: :
|
||||
> add a nice feature to the project
|
||||
#### Security :shield: :
|
||||
> security: fix a security issue on the Foo2 component
|
||||
|
||||
> security: fix another security problem on the Foo2 component
|
||||
#### Performance Enhancements :rocket: :
|
||||
> improve core performances by reducing complexity
|
||||
#### Style :art: :
|
||||
> reindent the full Foo class
|
||||
|
||||
### Options
|
||||
#### Display unknown messages types
|
||||
``` py
|
||||
from pychangelogfactory import ChangeLogFormater
|
||||
|
||||
raw_changelog = (
|
||||
"feat: add a nice feature to the project\n"
|
||||
"style: reindent the full Foo class\n"
|
||||
"security: fix a security issue on the Foo2 component\n"
|
||||
"security: fix another security problem on the Foo2 component\n"
|
||||
"improve core performances by reducing complexity\n"
|
||||
"some random changes in the text content\n"
|
||||
)
|
||||
changelog = ChangeLogFactory(self.raw_changelog).RenderFullChangelog(include_unknown=True)
|
||||
print(changelog)
|
||||
```
|
||||
### Output (rendered)
|
||||
#### Features :sparkles::
|
||||
> add a nice feature to the project
|
||||
#### Security :shield::
|
||||
> fix a security leak on the Foo2 component
|
||||
> fix a security issue on the Foo2 component
|
||||
> fix another security problem on the Foo2 component
|
||||
#### Performance Enhancements :rocket::
|
||||
> improve core performances by reducing complexity
|
||||
#### Style :art::
|
||||
> reindent the full Foo class
|
||||
> reindent the full Foo class
|
||||
#### Others :question::
|
||||
> some random changes in the text content
|
||||
|
||||
## Supported types
|
||||
|
||||
| Type/Tag | Priority | Keywords | Title |
|
||||
|-----------|----------|----------------------------------------|-------------------------------------------------------|
|
||||
| break | 20 | break | :rotating_light: Breaking changes :rotating_light: : |
|
||||
| feat | 20 | feat, new, create, add | Features :sparkles: : |
|
||||
| fix | 0 | fix, issue, problem | Fixes :wrench: : |
|
||||
| security | 20 | safe, leak | Security :shield: : |
|
||||
| chore | 10 | task, refactor, build, better, improve | Chore :building_construction: : |
|
||||
| perf | 15 | fast, perf | Performance Enhancements :rocket: : |
|
||||
| wip | 0 | temp | Work in progress changes :construction: : |
|
||||
| doc | 0 | doc, manual | Documentations :book: : |
|
||||
| style | 5 | beautify | Style :art: : |
|
||||
| refactor | 0 | | Refactorings :recycle: : |
|
||||
| ci | 0 | jenkins, git | Continuous Integration :cyclone: : |
|
||||
| test | -5 | unittest, check, testing | Testings :vertical_traffic_light: : |
|
||||
| build | 0 | compile, version | Builds :package: : |
|
||||
| revert | 0 | revert, fallback | Reverts :back: : |
|
||||
| other | -20 | | Others :question: : |
|
||||
|
||||
## Add new types
|
||||
|
||||
New formaters can be easily added by subclassing `ChangeLogFormater`:
|
||||
|
||||
### Inject custom formater locally (prefered way)
|
||||
|
||||
``` py
|
||||
from pychangelogfactory import ChangeLogFormater,ChangeLogFactory
|
||||
|
||||
class ChangeLogFormater_others(ChangeLogFormater):
|
||||
"""My formater"""
|
||||
|
||||
prefix: str = "mytag"
|
||||
title: str = "My Title :"
|
||||
keywords: list[str] = ["foo","42"]
|
||||
priority: int = 10
|
||||
|
||||
hdlr = ChangeLogFactory()
|
||||
hdlr.RegisterFormater(ChangeLogFormater_others)
|
||||
...
|
||||
```
|
||||
|
||||
### Inject custom formater module-wide
|
||||
|
||||
``` py
|
||||
from pychangelogfactory import ChangeLogFormater,ChangeLogFormaterRecordType
|
||||
|
||||
@ChangeLogFormaterRecordType
|
||||
class ChangeLogFormater_others(ChangeLogFormater):
|
||||
"""My formater"""
|
||||
|
||||
prefix: str = "mytag"
|
||||
title: str = "My Title :"
|
||||
keywords: list[str] = ["foo","42"]
|
||||
priority: int = 10
|
||||
|
||||
hdlr = ChangeLogFactory()
|
||||
...
|
||||
```
|
||||
|
||||
/// note | Scope
|
||||
This will register your new formater for all next new factories
|
||||
///
|
||||
### Test
|
||||
|
||||
``` py
|
||||
raw_changelog = ("mytag: add a nice feature to the project\n"
|
||||
"foo modification in my file\n"
|
||||
"need 42 coffee\n"
|
||||
)
|
||||
hdlr = ChangeLogFactory(raw_changelog)
|
||||
changelog = hdlr.RenderFullChangelog(include_unknown=True)
|
||||
print(changelog)
|
||||
```
|
||||
|
||||
### Output
|
||||
#### My Title :
|
||||
> add a nice feature to the project
|
||||
|
||||
> foo modification in my file
|
||||
|
||||
> need 42 coffee
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
16
mkdocs.yml
16
mkdocs.yml
@@ -1,11 +1,12 @@
|
||||
docs_dir: docs
|
||||
site_name: pychangelogfactory
|
||||
site_url: 'https://chacha.ddns.net/mkdocs-web/chacha/pychangelogfactory/latest/'
|
||||
site_description: 'A simple changelog builder that you can feed with your repository change history'
|
||||
site_url: https://chacha.ddns.net/mkdocs-web/chacha/pychangelogfactory/latest/
|
||||
site_description: A simple changelog builder that you can feed with your repository
|
||||
change history
|
||||
site_author: chacha
|
||||
repo_url: 'https://chacha.ddns.net/gitea/chacha/pychangelogfactory'
|
||||
repo_url: https://chacha.ddns.net/gitea/chacha/pychangelogfactory
|
||||
use_directory_urls: false
|
||||
copyright: 'CC BY-NC-SA 4.0'
|
||||
copyright: CC BY-NC-SA 4.0
|
||||
theme:
|
||||
name: material
|
||||
features:
|
||||
@@ -57,6 +58,13 @@ plugins:
|
||||
show_root_full_path: false
|
||||
merge_init_into_class: true
|
||||
separate_signature: true
|
||||
- with-pdf:
|
||||
cover_subtitle: User Manual
|
||||
cover_logo: C:\Users\chacha\git\pychangelogfactory\docs-static\Library.jpg
|
||||
verbose: false
|
||||
exclude_pages:
|
||||
- LICENSE
|
||||
output_path: C:\Users\chacha\git\pychangelogfactory\helpers-results\doc_gen\site\pdf\manual.pdf
|
||||
markdown_extensions:
|
||||
- def_list
|
||||
- tables
|
||||
|
||||
@@ -20,4 +20,4 @@ except PackageNotFoundError: # pragma: no cover
|
||||
warnings.warn("can not read __version__, assuming local test context, setting it to ?.?.?")
|
||||
__version__ = "?.?.?"
|
||||
|
||||
from .changelogfactory import ChangeLogFormater
|
||||
from .changelogfactory import ChangeLogFactory, ChangeLogFormaterRecordType, ChangeLogFormater
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
# You should have received a copy of the license along with this
|
||||
# work. If not, see <https://creativecommons.org/licenses/by-nc-sa/4.0/>.
|
||||
|
||||
"""A simple changelog formater that consume merged commit message and produce nice pre-formated changelogs.
|
||||
"""A simple changelog formater that consume merged message and produce nice pre-formated changelogs.
|
||||
|
||||
"""
|
||||
|
||||
@@ -25,83 +25,80 @@ def ChangeLogFormaterRecordType(Klass: type) -> type:
|
||||
Klass: class to register in the factory
|
||||
Returns:
|
||||
untouched class"""
|
||||
ChangeLogFormater.ar_Klass.append(Klass)
|
||||
ChangeLogFactory.ar_FormaterKlass.add(Klass)
|
||||
return Klass
|
||||
|
||||
|
||||
class ChangeLogFormater(ABC):
|
||||
"""The main changelog class that define nearly everythings.
|
||||
|
||||
This was supposed to be a very shorty script this is why it is all-in-one...
|
||||
/// warning
|
||||
Factory and base-objects are mixed in the same class.
|
||||
///
|
||||
"""
|
||||
|
||||
ar_Klass: list[ChangeLogFormater] = []
|
||||
ar_LinesResult: list[ChangeLogFormater] = []
|
||||
prefix: str = "^\s+"
|
||||
title: str = "Others :"
|
||||
checkCommentPattern: str = r"^[ \t]*(?:\/\/|#)"
|
||||
keywords: list[str] = []
|
||||
priority: int = 0
|
||||
|
||||
def __init__(self, scope: str | None, ChangelogString: str):
|
||||
"""Main ChangeLogFormater class constructor
|
||||
_lines: list[str] = []
|
||||
|
||||
def __init__(self):
|
||||
"""ChangeLogFormater class constructor
|
||||
|
||||
This class is the base formater class.
|
||||
|
||||
This class is for:
|
||||
|
||||
- classifying message: CheckLine() and CheckLine_keywords()
|
||||
- storing lines: Clear() and PushLine()
|
||||
- returning the formated output: Render() and RenderLines()
|
||||
|
||||
This class contain both formater and factory.
|
||||
|
||||
/// warning
|
||||
this class does not aim to be instantiated by user.
|
||||
///
|
||||
|
||||
"""
|
||||
self._lines: list[str] = []
|
||||
|
||||
def Clear(self) -> None:
|
||||
"""Clear the formater content"""
|
||||
self._lines: list[str] = []
|
||||
|
||||
def PushLine(self, ChangelogString: str) -> None:
|
||||
"""Push a new line in the formater
|
||||
|
||||
Args:
|
||||
scope: scope of the formater (tag)
|
||||
ChangelogString: formater rendered title
|
||||
ChangelogString: the new line to insert
|
||||
"""
|
||||
self._scope = scope
|
||||
self._ChangelogString = ChangelogString.strip()
|
||||
self._lines.append(ChangelogString.strip())
|
||||
|
||||
def RenderLine(self):
|
||||
"""Get a rendered line
|
||||
Returns:
|
||||
the rendered line
|
||||
"""
|
||||
return self._ChangelogString.strip()
|
||||
|
||||
@classmethod
|
||||
def RenderLines(cls) -> str:
|
||||
"""Render all lines
|
||||
def Render(self) -> str:
|
||||
"""Render all lines + title
|
||||
Returns:
|
||||
the rendered lines
|
||||
"""
|
||||
changelog_category: str = ""
|
||||
lines = cls.GetLines()
|
||||
if len(lines) > 0:
|
||||
changelog_category = f"#### {cls.title}\n"
|
||||
for line in lines:
|
||||
changelog_category = changelog_category + f"> {line.RenderLine()}"
|
||||
if (scope := line.GetScope()) != "":
|
||||
changelog_category = changelog_category + f"\t*[{scope}]*"
|
||||
changelog_category = changelog_category + "\n"
|
||||
changelog_category = ""
|
||||
if len(self._lines) > 0:
|
||||
changelog_category = f"#### {self.title}\n"
|
||||
changelog_category = changelog_category + self.RenderLines()
|
||||
return changelog_category
|
||||
|
||||
def GetScope(self) -> str:
|
||||
"""Return the current scope (category)
|
||||
def RenderLines(self) -> str:
|
||||
"""Render only lines
|
||||
Returns:
|
||||
the current scope
|
||||
the rendered lines
|
||||
"""
|
||||
return self._scope if self._scope is not None else ""
|
||||
|
||||
@classmethod
|
||||
def Clear(cls) -> None:
|
||||
"""Clear internal memory"""
|
||||
ChangeLogFormater.ar_LinesResult = []
|
||||
full_lines = ""
|
||||
for line in self._lines:
|
||||
full_lines = full_lines + f"> {line}" + "\n"
|
||||
return full_lines
|
||||
|
||||
@classmethod
|
||||
def CheckLine(cls, content: str) -> re.Match:
|
||||
"""Check if a line is in the current scope (lazy identification)
|
||||
only formal tags are parsed by this function
|
||||
eg: <change_type>(<change_target>): <change_message>
|
||||
"""Check if a line match the current formater (lazy identification)
|
||||
|
||||
/// warning
|
||||
Only formal tags are parsed by this function
|
||||
eg: `<change_type>(<change_target>): <change_message>`
|
||||
///
|
||||
|
||||
Args:
|
||||
content: line to parse
|
||||
Returns:
|
||||
@@ -113,13 +110,14 @@ class ChangeLogFormater(ABC):
|
||||
|
||||
@classmethod
|
||||
def CheckLine_keywords(cls, content: str) -> bool:
|
||||
"""Check if a line is in the current scope (deeper in-word identification)
|
||||
any word in the message can be used to categorize this message.
|
||||
this function test only for the current category.
|
||||
"""Check if a line match the current formater (deeper in-content identification)
|
||||
|
||||
Any word in the message can be used to categorize this message.
|
||||
|
||||
Args:
|
||||
content: line to parse
|
||||
Returns:
|
||||
True if a keyword has matched
|
||||
True if a keyword has matched, False otherwise
|
||||
"""
|
||||
keyword_list = cls.keywords
|
||||
for _keyword in keyword_list:
|
||||
@@ -127,91 +125,130 @@ class ChangeLogFormater(ABC):
|
||||
return True
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
def FactoryProcessLineMain(cls, RawChangelogLine: str) -> ChangeLogFormater:
|
||||
|
||||
class ChangeLogFactory:
|
||||
"""The main changelog class"""
|
||||
|
||||
ar_FormaterKlass: set[type[ChangeLogFormater]] = set()
|
||||
ar_Formater: dict[ChangeLogFormater] = dict()
|
||||
checkCommentPattern: str = r"^[ \t]*(?:\/\/|#)"
|
||||
|
||||
def __init__(self, ChangelogString: None | str = None):
|
||||
"""Main ChangeLogFormater class constructor
|
||||
|
||||
Args:
|
||||
ChangelogString: optionnal input string to start with
|
||||
"""
|
||||
for FormaterKlass in ChangeLogFactory.ar_FormaterKlass:
|
||||
self.ar_Formater[FormaterKlass.__name__] = FormaterKlass()
|
||||
|
||||
if isinstance(ChangelogString, str):
|
||||
self.ProcessFullChangelog(ChangelogString)
|
||||
|
||||
def RegisterFormater(self, FormaterKlass: ChangeLogFormater) -> None:
|
||||
"""Register a new formater
|
||||
|
||||
Args:
|
||||
FormaterKlass: class of the formater to be added
|
||||
Returns:
|
||||
self class for convenience
|
||||
"""
|
||||
self.ar_FormaterKlass.add(FormaterKlass)
|
||||
self.ar_Formater[FormaterKlass.__name__] = FormaterKlass()
|
||||
return self
|
||||
|
||||
def unRegisterFormater(self, FormaterKlass: ChangeLogFormater) -> None:
|
||||
"""Register a new formater
|
||||
|
||||
Args:
|
||||
FormaterKlass: class of the formater to be removed
|
||||
Returns:
|
||||
self class for convenience
|
||||
"""
|
||||
self.ar_FormaterKlass.remove(FormaterKlass)
|
||||
del self.ar_Formater[FormaterKlass.__name__]
|
||||
return self
|
||||
|
||||
def Clear(self) -> ChangeLogFactory:
|
||||
"""Clear internal memory
|
||||
Returns:
|
||||
self class for convenience
|
||||
"""
|
||||
for formater in self.ar_Formater.values():
|
||||
formater.Clear()
|
||||
return self
|
||||
|
||||
def _ProcessLineMain(self, RawChangelogLine: str) -> bool:
|
||||
"""Process a line and look for identified ones
|
||||
this function will try to apply every available formater for the 1st search round: formal search
|
||||
order of search is set according to formater's configuration
|
||||
|
||||
This function will try to apply every available formater for the 1st search round: formal search
|
||||
If a matching formater is found, line is inserted.
|
||||
|
||||
Args:
|
||||
RawChangelogLine: line to parse
|
||||
Returns:
|
||||
a corresponding ChangeLogFormater_XXX() object, or a ChangeLogFormater_others()
|
||||
True if successfully matched, False otherwise
|
||||
"""
|
||||
for Klass in sorted(ChangeLogFormater.ar_Klass, key=lambda x: x.priority):
|
||||
content = Klass.CheckLine(RawChangelogLine)
|
||||
for formater in sorted(self.ar_Formater.values(), key=lambda x: x.priority):
|
||||
content = formater.CheckLine(RawChangelogLine)
|
||||
if content is not None:
|
||||
return Klass(content.group(1), content.group(2))
|
||||
return ChangeLogFormater_others(None, RawChangelogLine)
|
||||
formater.PushLine(content.group(2))
|
||||
return True
|
||||
|
||||
@classmethod
|
||||
def FactoryProcessLineSecond(cls, RawChangelogLine: str) -> ChangeLogFormater:
|
||||
return False
|
||||
|
||||
def _ProcessLineSecond(self, RawChangelogLine: str) -> bool:
|
||||
"""Process a line and look for non-identified ones
|
||||
this function will try to apply every available formater for the 2ns search round: any keyword
|
||||
order of search is set according to formater's configuration
|
||||
|
||||
This function will try to apply every available formater for the 2ns search round: any keyword
|
||||
If a matching formater is found, line is inserted.
|
||||
|
||||
Args:
|
||||
RawChangelogLine: line to parse
|
||||
Returns:
|
||||
a corresponding ChangeLogFormater_XXX() object, or a ChangeLogFormater_others()
|
||||
True if successfully matched, False otherwise
|
||||
"""
|
||||
for Klass in sorted(ChangeLogFormater.ar_Klass, key=lambda x: x.priority, reverse=True):
|
||||
if Klass.CheckLine_keywords(RawChangelogLine):
|
||||
return Klass(None, RawChangelogLine)
|
||||
for formater in sorted(self.ar_Formater.values(), key=lambda x: x.priority, reverse=True):
|
||||
if formater.CheckLine_keywords(RawChangelogLine):
|
||||
formater.PushLine(RawChangelogLine)
|
||||
return True
|
||||
|
||||
return ChangeLogFormater_others(None, RawChangelogLine)
|
||||
self.ar_Formater[ChangeLogFormater_others.__name__].PushLine(RawChangelogLine)
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
def FactoryProcessFullChangelog(cls, RawChangelogMessage: str) -> list[ChangeLogFormater]:
|
||||
def ProcessFullChangelog(self, RawChangelogMessage: str) -> ChangeLogFactory:
|
||||
"""Process all input lines
|
||||
This function handle the main 2-round changes search algo.
|
||||
Tt takes care of search-order and automatically skip any non-relevants message line.
|
||||
|
||||
This function handles the main 2-round changes search algo.
|
||||
It takes care of search-order and automatically skip any non-relevants message line.
|
||||
A non relevant line can be a commented one, or a to short one.
|
||||
Available comment patterns are: // and #
|
||||
A relevant commit line must contain:
|
||||
- at least 2 words for formal
|
||||
- at least 3 words for keywords
|
||||
|
||||
Args:
|
||||
RawChangelogMessage: The full raw changelog (merged commit-history)
|
||||
Returns:
|
||||
a list of ChangeLogFormater_XXX() object
|
||||
self class for convenience
|
||||
"""
|
||||
LinesResult = []
|
||||
|
||||
Lines2ndRound = []
|
||||
|
||||
for line in RawChangelogMessage.split("\n"):
|
||||
lineWordsCount = len(line.split())
|
||||
if (lineWordsCount > 1) and (not re.match(cls.checkCommentPattern, line)):
|
||||
res = cls.FactoryProcessLineMain(line)
|
||||
if type(res) is not ChangeLogFormater_others:
|
||||
LinesResult.append(res)
|
||||
if (lineWordsCount > 1) and (not re.match(self.checkCommentPattern, line)):
|
||||
if self._ProcessLineMain(line) is True:
|
||||
continue
|
||||
elif lineWordsCount > 2:
|
||||
Lines2ndRound.append(line)
|
||||
|
||||
for line in Lines2ndRound:
|
||||
LinesResult.append(cls.FactoryProcessLineSecond(line))
|
||||
self._ProcessLineSecond(line)
|
||||
|
||||
ChangeLogFormater.ar_LinesResult = LinesResult
|
||||
return ChangeLogFormater.ar_LinesResult
|
||||
return self
|
||||
|
||||
@classmethod
|
||||
def GetLinesOfType(cls, Klass: type) -> list[ChangeLogFormater]:
|
||||
"""Retrieve all lines of specified formater type
|
||||
Args:
|
||||
Klass: type of formater to get
|
||||
Returns:
|
||||
a list of ChangeLogFormater_XXX() object
|
||||
"""
|
||||
return [_ for _ in ChangeLogFormater.ar_LinesResult if isinstance(_, Klass)]
|
||||
|
||||
@classmethod
|
||||
def GetLines(cls) -> list[ChangeLogFormater]:
|
||||
"""Retrieve all lines for the current formater
|
||||
Returns:
|
||||
a list of ChangeLogFormater_XXX() object
|
||||
"""
|
||||
return ChangeLogFormater.GetLinesOfType(cls)
|
||||
|
||||
@classmethod
|
||||
def RenderFullChangelog(cls, include_unknown: bool = False) -> str:
|
||||
def RenderFullChangelog(self, include_unknown: bool = False) -> str:
|
||||
"""Render the main changelog
|
||||
Args:
|
||||
include_unknown: includes unknown lines in an Unknown category
|
||||
@@ -219,10 +256,11 @@ class ChangeLogFormater(ABC):
|
||||
the final formated changelog
|
||||
"""
|
||||
full_changelog = ""
|
||||
for Klass in sorted(ChangeLogFormater.ar_Klass, key=lambda x: x.priority, reverse=True):
|
||||
if (include_unknown is False) and (Klass == ChangeLogFormater_others):
|
||||
for formater in sorted(self.ar_Formater.values(), key=lambda x: x.priority, reverse=True):
|
||||
if (include_unknown is False) and (isinstance(formater, ChangeLogFormater_others)):
|
||||
continue
|
||||
full_changelog = full_changelog + Klass.RenderLines()
|
||||
full_changelog = full_changelog + formater.Render()
|
||||
|
||||
return full_changelog
|
||||
|
||||
|
||||
@@ -237,43 +275,43 @@ class ChangeLogFormater(ABC):
|
||||
for RecordType, Config in {
|
||||
# fmt: off
|
||||
"break": ( 20, ["break"],
|
||||
":rotating_light: Breaking changes :rotating_light::",
|
||||
":rotating_light: Breaking changes :rotating_light: :",
|
||||
),
|
||||
"feat": ( 20, ["feat", "new", "create", "add"],
|
||||
"Features :sparkles::"
|
||||
"feat": ( 25, ["feat", "new", "create", "add"],
|
||||
"Features :sparkles: :"
|
||||
),
|
||||
"fix": ( 0, ["fix","issue", "problem"],
|
||||
"Fixes :wrench::"
|
||||
"fix": ( 0, ["fix","issue", "problem"],
|
||||
"Fixes :wrench: :"
|
||||
),
|
||||
"security": ( 20, ["safe", "leak"],
|
||||
"Security :shield::"
|
||||
"Security :shield: :"
|
||||
),
|
||||
"chore": ( 20, ["task", "refactor", "build", "better", "improve"],
|
||||
"Chore :building_construction::",
|
||||
"chore": ( 10, ["task", "refactor", "build", "better", "improve"],
|
||||
"Chore :building_construction: :",
|
||||
),
|
||||
"perf": ( 0, ["fast", ],
|
||||
"Performance Enhancements :rocket::",
|
||||
"perf": ( 15, ["fast","perf" ],
|
||||
"Performance Enhancements :rocket: :",
|
||||
),
|
||||
"wip": ( 0, ["temp", ],
|
||||
"Work in progress changes :construction::",
|
||||
"Work in progress changes :construction: :",
|
||||
),
|
||||
"docs": ( 0, [ "doc", ],
|
||||
"Documentations :book::",
|
||||
"doc": ( 0, [ "doc", "manual"],
|
||||
"Documentations :book: :",
|
||||
),
|
||||
"style": ( 5, ["beautify", ],
|
||||
"Style :art::",
|
||||
"Style :art: :",
|
||||
),
|
||||
"refactor": ( 0, [],
|
||||
"Refactorings :recycle::"
|
||||
"Refactorings :recycle: :"
|
||||
),
|
||||
"ci": ( 0, ["jenkins", "git"],
|
||||
"Continuous Integration :cyclone::"
|
||||
"Continuous Integration :cyclone: :"
|
||||
),
|
||||
"test": ( -5, ["unittest", "check", r"^(?:\s)*test(?:\s)*$"],
|
||||
"Testings :vertical_traffic_light::"
|
||||
"test": ( -5, ["unittest", "check", "testing"],
|
||||
"Testings :vertical_traffic_light: :"
|
||||
),
|
||||
"build": ( 0, ["compile", "version"],
|
||||
"Builds :package:"
|
||||
"Builds :package: :"
|
||||
),
|
||||
# fmt: on
|
||||
}.items():
|
||||
@@ -289,7 +327,7 @@ for RecordType, Config in {
|
||||
"priority": Config[0],
|
||||
},
|
||||
)
|
||||
ChangeLogFormater.ar_Klass.append(_tmp)
|
||||
ChangeLogFactory.ar_FormaterKlass.add(_tmp)
|
||||
|
||||
|
||||
@ChangeLogFormaterRecordType
|
||||
@@ -297,16 +335,19 @@ class ChangeLogFormater_revert(ChangeLogFormater):
|
||||
"""Revert scope formater"""
|
||||
|
||||
prefix: str = "revert"
|
||||
title: str = "Reverts :back::"
|
||||
keywords: list[str] = ["fallback"]
|
||||
title: str = "Reverts :back: :"
|
||||
keywords: list[str] = ["revert", "fallback"]
|
||||
priority: int = 0
|
||||
|
||||
def RenderLine(self) -> str:
|
||||
"""an overloaded RenderLine implementation that adds surrounding '~~'
|
||||
def RenderLines(self) -> str:
|
||||
"""Render all lines
|
||||
Returns:
|
||||
the rendered pattern
|
||||
the rendered lines
|
||||
"""
|
||||
return "~~" + super().RenderLine() + "~~"
|
||||
full_lines = ""
|
||||
for line in self._lines:
|
||||
full_lines = full_lines + f"> ~~{line}~~" + "\n"
|
||||
return full_lines
|
||||
|
||||
|
||||
@ChangeLogFormaterRecordType
|
||||
@@ -314,6 +355,6 @@ class ChangeLogFormater_others(ChangeLogFormater):
|
||||
"""Others / unknown scope formater"""
|
||||
|
||||
prefix: str = "other"
|
||||
title: str = "Others :question::"
|
||||
title: str = "Others :question: :"
|
||||
keywords: list[str] = [""]
|
||||
priority: int = -20
|
||||
|
||||
@@ -13,16 +13,17 @@ from src import pychangelogfactory
|
||||
|
||||
class Testtest_module(unittest.TestCase):
|
||||
def simplegeneration(self, inputstr, teststrs: list[str]):
|
||||
pychangelogfactory.ChangeLogFormater.FactoryProcessFullChangelog(inputstr)
|
||||
changelog = pychangelogfactory.ChangeLogFormater.RenderFullChangelog()
|
||||
hdlr = pychangelogfactory.ChangeLogFactory()
|
||||
hdlr.ProcessFullChangelog(inputstr)
|
||||
changelog = hdlr.RenderFullChangelog()
|
||||
for test in teststrs:
|
||||
self.assertIn(test, changelog)
|
||||
|
||||
def test_simplegeneration_ignored2(self):
|
||||
raw = "break: testbreak break" + "\n" + "#docs: testdoc doc" + "\n" + "#style: teststyle beautify" + "\n" + "//test: testtest check"
|
||||
raw = "break: testbreak break" + "\n" + "#doc: testdoc doc" + "\n" + "#style: teststyle beautify" + "\n" + "//test: testtest check"
|
||||
|
||||
pychangelogfactory.ChangeLogFormater.FactoryProcessFullChangelog(raw)
|
||||
changelog = pychangelogfactory.ChangeLogFormater.RenderFullChangelog()
|
||||
hdlr = pychangelogfactory.ChangeLogFactory(raw)
|
||||
changelog = hdlr.RenderFullChangelog()
|
||||
|
||||
self.assertIn("testbreak", changelog)
|
||||
self.assertNotIn("testdoc", changelog)
|
||||
@@ -30,26 +31,26 @@ class Testtest_module(unittest.TestCase):
|
||||
self.assertNotIn("testtest", changelog)
|
||||
|
||||
def test_simplegeneration_ignored(self):
|
||||
raw = "break: testbreak" + "\n" + "#docs: testdoc" + "\n" + "#style: teststyle" + "\n" + "//test: testtest"
|
||||
raw = "break: testbreak" + "\n" + "#doc: testdoc" + "\n" + "#style: teststyle" + "\n" + "//test: testtest"
|
||||
|
||||
pychangelogfactory.ChangeLogFormater.FactoryProcessFullChangelog(raw)
|
||||
changelog = pychangelogfactory.ChangeLogFormater.RenderFullChangelog()
|
||||
hdlr = pychangelogfactory.ChangeLogFactory(raw)
|
||||
changelog = hdlr.RenderFullChangelog()
|
||||
self.assertIn("testbreak", changelog)
|
||||
self.assertNotIn("testdoc", changelog)
|
||||
self.assertNotIn("teststyle", changelog)
|
||||
self.assertNotIn("testtest", changelog)
|
||||
|
||||
def test_simplegeneration_order(self):
|
||||
raw = "break: testbreak" + "\n" + "docs: testdoc" + "\n" + "style: teststyle" + "\n" + "test: testtest"
|
||||
pychangelogfactory.ChangeLogFormater.FactoryProcessFullChangelog(raw)
|
||||
changelog = pychangelogfactory.ChangeLogFormater.RenderFullChangelog().splitlines()
|
||||
raw = "break: testbreak" + "\n" + "doc: testdoc" + "\n" + "style: teststyle" + "\n" + "test: testtest"
|
||||
hdlr = pychangelogfactory.ChangeLogFactory(raw)
|
||||
changelog = hdlr.RenderFullChangelog().splitlines()
|
||||
self.assertIn("testbreak", changelog[1])
|
||||
self.assertIn("teststyle", changelog[3])
|
||||
self.assertIn("testdoc", changelog[5])
|
||||
self.assertIn("testtest", changelog[7])
|
||||
|
||||
def test_simplegeneration_multiple(self):
|
||||
raw = "break: testbreak" + "\n" + "docs: testdoc" + "\n" + "style: teststyle"
|
||||
raw = "break: testbreak" + "\n" + "doc: testdoc" + "\n" + "style: teststyle"
|
||||
self.simplegeneration(raw, ["testbreak", "testdoc", "teststyle"])
|
||||
|
||||
def test_simplegeneration_breaking(self):
|
||||
@@ -90,7 +91,7 @@ class Testtest_module(unittest.TestCase):
|
||||
self.simplegeneration("test temp dummy1 dummy2", ["test temp"])
|
||||
|
||||
def test_simplegeneration_docs(self):
|
||||
self.simplegeneration("docs: teststring", ["teststring"])
|
||||
self.simplegeneration("doc: teststring", ["teststring"])
|
||||
self.simplegeneration("test doc dummy1 dummy2", ["test doc"])
|
||||
|
||||
def test_simplegeneration_style(self):
|
||||
@@ -118,22 +119,74 @@ class Testtest_module(unittest.TestCase):
|
||||
def test_simplegeneration_revert(self):
|
||||
self.simplegeneration("revert: teststring", ["~~teststring~~"])
|
||||
|
||||
# fmt: off
|
||||
raw_changelog = (
|
||||
"feat: add a nice feature to the project\n"
|
||||
"style: reindent the full Foo class\n"
|
||||
"security: fix a security issue on the Foo2 component\n"
|
||||
"security: fix another security problem on the Foo2 component\n"
|
||||
"improve core performances by reducing complexity\n"
|
||||
"some random changes in the text content\n"
|
||||
)
|
||||
expected_formated = (
|
||||
"#### Features :sparkles: :\n"
|
||||
"> add a nice feature to the project\n"
|
||||
"#### Security :shield: :\n"
|
||||
"> fix a security issue on the Foo2 component\n"
|
||||
"> fix another security problem on the Foo2 component\n"
|
||||
"#### Performance Enhancements :rocket: :\n"
|
||||
"> improve core performances by reducing complexity\n"
|
||||
"#### Style :art: :\n"
|
||||
"> reindent the full Foo class\n"
|
||||
"#### Others :question: :\n"
|
||||
"> some random changes in the text content\n"
|
||||
)
|
||||
# fmt: on
|
||||
|
||||
def test_sample(self):
|
||||
raw_changelog = (
|
||||
"feat: add a nice feature to the project\n"
|
||||
"style: reindent the full Foo class\n"
|
||||
"security: fix a security leak on the Foo2 component"
|
||||
)
|
||||
pychangelogfactory.ChangeLogFormater.FactoryProcessFullChangelog(raw_changelog)
|
||||
changelog = pychangelogfactory.ChangeLogFormater.RenderFullChangelog()
|
||||
hdlr = pychangelogfactory.ChangeLogFactory(self.raw_changelog)
|
||||
changelog = hdlr.RenderFullChangelog(include_unknown=True)
|
||||
self.assertEqual(changelog, self.expected_formated)
|
||||
|
||||
def test_sample_aio(self):
|
||||
changelog = pychangelogfactory.ChangeLogFactory(self.raw_changelog).RenderFullChangelog(include_unknown=True)
|
||||
print(changelog)
|
||||
self.assertEqual(changelog, self.expected_formated)
|
||||
|
||||
def test_sample_exploded(self):
|
||||
hdlr = pychangelogfactory.ChangeLogFactory()
|
||||
hdlr.ProcessFullChangelog(self.raw_changelog)
|
||||
changelog = hdlr.RenderFullChangelog(include_unknown=True)
|
||||
self.assertEqual(changelog, self.expected_formated)
|
||||
|
||||
|
||||
class Testtest_module_othercontext(unittest.TestCase):
|
||||
def test_custom(self):
|
||||
|
||||
from src.pychangelogfactory import ChangeLogFormater, ChangeLogFormaterRecordType, ChangeLogFactory
|
||||
|
||||
@ChangeLogFormaterRecordType
|
||||
class ChangeLogFormater_TEST(ChangeLogFormater):
|
||||
"""My formater"""
|
||||
|
||||
prefix: str = "mytag"
|
||||
title: str = "My Title :"
|
||||
keywords: list[str] = ["foo", "42"]
|
||||
priority: int = 10
|
||||
|
||||
# fmt: off
|
||||
raw_changelog = ("mytag: add a nice feature to the project\n"
|
||||
"foo modification in my file\n"
|
||||
"need 42 coffee\n"
|
||||
)
|
||||
expected_formated = (
|
||||
"#### Features :sparkles::\n"
|
||||
"> add a nice feature to the project\n"
|
||||
"#### Security :shield::\n"
|
||||
"> fix a security leak on the Foo2 component\n"
|
||||
"#### Style :art::\n"
|
||||
"> reindent the full Foo class\n"
|
||||
"#### My Title :\n"
|
||||
"> add a nice feature to the project\n"
|
||||
"> foo modification in my file\n"
|
||||
"> need 42 coffee\n"
|
||||
)
|
||||
# fmt: on
|
||||
|
||||
hdlr = ChangeLogFactory(raw_changelog)
|
||||
changelog = hdlr.RenderFullChangelog(include_unknown=True)
|
||||
self.assertEqual(changelog, expected_formated)
|
||||
|
||||
Reference in New Issue
Block a user