diff --git a/.pydevproject b/.pydevproject index 9452bc3..2c06e15 100644 --- a/.pydevproject +++ b/.pydevproject @@ -1,17 +1,20 @@ - + + Default - + + python interpreter - + + - /${PROJECT_DIR_NAME}/src /${PROJECT_DIR_NAME} - + + diff --git a/.settings/org.eclipse.core.resources.prefs b/.settings/org.eclipse.core.resources.prefs index 5e838b3..99f26c0 100644 --- a/.settings/org.eclipse.core.resources.prefs +++ b/.settings/org.eclipse.core.resources.prefs @@ -1,3 +1,2 @@ eclipse.preferences.version=1 -encoding//src/pychangelogfactory/changelogfactory.py=utf-8 encoding/=UTF-8 diff --git a/README.md b/README.md index 94685df..215c798 100644 --- a/README.md +++ b/README.md @@ -10,8 +10,7 @@ # pyChangeLogHelper -A simple changelog formater that consume merged commit message and produce nice pre-formated changelogs +A simple changelog formater that consume raw changes list text and produce nice pre-formated changelogs. +The input data mainly aim to be a merged commit report. -Checkout [Latest Documentation](https://chacha.ddns.net/mkdocs-web/chacha/pychangelogfactory/master/latest/). - -## Features +Checkout [Latest Documentation](https://chacha.ddns.net/mkdocs-web/chacha/pychangelogfactory/master/latest/). \ No newline at end of file diff --git a/RUN_changelog.launch b/RUN_changelog.launch new file mode 100644 index 0000000..22d81bf --- /dev/null +++ b/RUN_changelog.launch @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/RUN_complexity.launch b/RUN_complexity.launch new file mode 100644 index 0000000..55ecc1c --- /dev/null +++ b/RUN_complexity.launch @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/RUN_mkdocs.launch b/RUN_mkdocs.launch new file mode 100644 index 0000000..7816ecf --- /dev/null +++ b/RUN_mkdocs.launch @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/RUN_quality.launch b/RUN_quality.launch new file mode 100644 index 0000000..689dcff --- /dev/null +++ b/RUN_quality.launch @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/RUN_unittest.launch b/RUN_unittest.launch new file mode 100644 index 0000000..98bd546 --- /dev/null +++ b/RUN_unittest.launch @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/docs-static/usage.md b/docs-static/usage.md index 06f0375..472d0d0 100644 --- a/docs-static/usage.md +++ b/docs-static/usage.md @@ -1,5 +1,28 @@ # Usage +## Theory + +This lib will try to extract normalized changes from a given raw history. + +_It's up to the user to provide this merged history._ + +To realize this job, parsing is done in two rounds: + +- first round extrats formal changes messages: e.g.: `(): ` +- secound round extracts lines from remaining ones, based on keywords dictionnaries + +/// warning | searching policy +When formal search (1), _lines must contain at least 2 words_ +When keywords search (2), _lines must contain at least 3 words_ +/// + +/// note | ignored lines +lines with comment tags are ignored: + +- `[0..N space]//` +- `[0..N space]#` +/// + ## Installation From pypi repository (prefered): @@ -15,10 +38,34 @@ From master git repository: python -m pip install git+https://chacha.ddns.net/gitea/chacha/pychangelogfactory.git@master +## Use in your project +### Sample code +``` py +from pychangelogfactory import ChangeLogFormater -## Import in your project +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() +print(changelog) +``` +### Output(Raw) -Add this line on the top of your python script: - - from pychangelogfactory import ChangeLogFormater + #### Features :sparkles:: + > add a nice feature to the project + #### Security :shield:: + > fix a security leak on the Foo2 component + #### Style :art:: + > reindent the full Foo class + +### Output (rendered) +#### Features :sparkles:: +> add a nice feature to the project +#### Security :shield:: +> fix a security leak on the Foo2 component +#### Style :art:: +> reindent the full Foo class \ No newline at end of file diff --git a/helpers/doc_gen.py b/helpers/doc_gen.py index 155db4d..232278e 100644 --- a/helpers/doc_gen.py +++ b/helpers/doc_gen.py @@ -72,9 +72,11 @@ class doc_gen(helper_withresults_base): # little hack here, to enable / disable pdf generation using own class config # => reason is mkdocs seems to try loading the plugin even if we disable it, so we need to # manually process the configuration file. - mkdocsCfg = None with open(cls.project_rootdir_path / "mkdocs.yml", "r") as mkdocsCfgFile: - mkdocsCfg = yaml.load(mkdocsCfgFile, Loader=yaml.SafeLoader) + mkdocsCfg = yaml.load(mkdocsCfgFile, Loader=yaml.Loader) + + if "plugins" in mkdocsCfg: + mkdocsCfg["plugins"] = [_ for _ in mkdocsCfg["plugins"] if (not isinstance(_, dict) or "with-pdf" not in _.keys())] if cls.enable_gen_pdf == True: mkdocsCfg["plugins"].append( @@ -83,19 +85,11 @@ class doc_gen(helper_withresults_base): "cover_subtitle": "User Manual", "cover_logo": str(cls.project_rootdir_path / "docs-static" / "Library.jpg"), "verbose": False, - "media_type": "print", "exclude_pages": ["LICENSE"], "output_path": str(site_path / "pdf" / "manual.pdf"), } } ) - else: - for subelem in mkdocsCfg["plugins"]: - if isinstance(subelem, dict): - if "with-pdf" in subelem.keys(): - mkdocsCfg["plugins"].remove(subelem) - break - with open(cls.project_rootdir_path / "mkdocs.yml", "w") as mkdocsCfgFile: mkdocsCfgFile.write(yaml.dump(mkdocsCfg, Dumper=Dumper, default_flow_style=False, sort_keys=False)) diff --git a/mkdocs.yml b/mkdocs.yml index f8fa45d..b474ff2 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -1,92 +1,110 @@ -# pyChaChaDummyProject (c) by chacha -# -# pyChaChaDummyProject is licensed under a -# Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International Unported License. -# -# You should have received a copy of the license along with this -# work. If not, see . - 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: - - navigation.instant - - navigation.tracking - - navigation.tabs - - navigation.tabs.sticky - - toc.integrate - - navigation.top + - navigation.instant + - navigation.tracking + - navigation.tabs + - navigation.tabs.sticky + - navigation.footer + - toc.integrate + - navigation.top + - navigation.section + - content.code.annotate + - navigation.prune + - toc.follow palette: - - media: '(prefers-color-scheme: dark)' - scheme: slate - toggle: - icon: material/brightness-4 - name: Switch to system preference - - media: (prefers-color-scheme) - toggle: - icon: material/brightness-auto - name: Switch to light mode - - media: '(prefers-color-scheme: light)' - scheme: default - toggle: - icon: material/brightness-7 - name: Switch to dark mode + - media: '(prefers-color-scheme: dark)' + scheme: slate + toggle: + icon: material/brightness-4 + name: Switch to system preference + - media: (prefers-color-scheme) + toggle: + icon: material/brightness-auto + name: Switch to light mode + - media: '(prefers-color-scheme: light)' + scheme: default + toggle: + icon: material/brightness-7 + name: Switch to dark mode plugins: - - search - - markdownextradata - - mermaid2 - - localsearch - - autorefs - - mkdocstrings: - default_handler: python - handlers: - python: - selection: - filters: - - '!^_(?!_init__)' - inherited_members: true - rendering: - show_root_heading: false - show_root_toc_entry: false - show_root_full_path: false - show_if_no_docstring: true - show_signature_annotations: true - show_source: false - heading_level: 2 - group_by_category: true - show_category_heading: true +- search +- markdownextradata +- mermaid2 +- localsearch +- mkdocstrings: + default_handler: python + handlers: + python: + options: + filters: + - '!^_[^_]' + inherited_members: true + show_if_no_docstring: true + show_signature_annotations: true + show_source: false + show_category_heading: true + group_by_category: true + docstring_section_style: spacy + show_root_full_path: false + merge_init_into_class: true + separate_signature: true markdown_extensions: - - def_list - - tables - - attr_list - - abbr - - pymdownx.betterem: - smart_enable: all - - pymdownx.caret - - pymdownx.critic - - pymdownx.details - - pymdownx.inlinehilite - - pymdownx.snippets - - pymdownx.highlight: - anchor_linenums: true - line_spans: __span - pygments_lang_class: true - - pymdownx.keys - - pymdownx.mark - - pymdownx.progressbar - - pymdownx.smartsymbols - - pymdownx.tasklist: - custom_checkbox: true - - pymdownx.tilde - - footnotes - +- def_list +- tables +- attr_list +- abbr +- pymdownx.blocks.admonition: + types: + - new + - settings + - note + - abstract + - info + - tip + - success + - question + - warning + - failure + - danger + - bug + - example + - quote +- pymdownx.blocks.definition +- pymdownx.blocks.details +- pymdownx.blocks.tab +- pymdownx.blocks.html +- pymdownx.betterem: + smart_enable: all +- pymdownx.caret +- pymdownx.critic +- pymdownx.details +- pymdownx.inlinehilite +- pymdownx.snippets +- pymdownx.highlight: + anchor_linenums: true + line_spans: __span + pygments_lang_class: true +- pymdownx.keys +- pymdownx.mark +- pymdownx.progressbar +- pymdownx.smartsymbols +- pymdownx.tasklist: + custom_checkbox: true +- pymdownx.tilde +- footnotes +- pymdownx.superfences +- pymdownx.emoji: + emoji_index: !!python/name:materialx.emoji.twemoji + emoji_generator: !!python/name:materialx.emoji.to_svg extra: branch: master - repository: pygitversionhelper \ No newline at end of file + repository: pygitversionhelper diff --git a/pyproject.toml b/pyproject.toml index f689a2b..2475436 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -59,7 +59,7 @@ coverage-check = ["coverage>=7.0"] complexity-check = ["radon>=5.1"] quality-check = ["pylint>=2.15","pylint-json2html>=0.4","pandas>=1.5"] type-check = ["mypy[reports]>=0.99" ] -doc-gen = ["mkdocs>=1.4.0", "mkdocs-material>=8.5", "mkdocs-localsearch>=0.9.0", "mkdocstrings[python]>=0.19", "mkdocs-with-pdf>=0.9.3","pyyaml>=6.0","pymdown-extensions>=9","mkdocs-markdownextradata-plugin","mkdocs-mermaid2-plugin"] +doc-gen = ["mkdocs>=1.4.0", "mkdocs-material>=8.5","mkdocs-pymdownx-material-extras", "mkdocs-localsearch>=0.9.0", "mkdocstrings[python]>=0.19", "mkdocs-with-pdf>=0.9.3","pyyaml>=6.0","pymdown-extensions>=9","mkdocs-markdownextradata-plugin","mkdocs-mermaid2-plugin"] #[project.scripts] #my-script = "my_package.module:function" diff --git a/src/pychangelogfactory/changelogfactory.py b/src/pychangelogfactory/changelogfactory.py index 99ab8bb..e900e28 100644 --- a/src/pychangelogfactory/changelogfactory.py +++ b/src/pychangelogfactory/changelogfactory.py @@ -9,7 +9,7 @@ # You should have received a copy of the license along with this # work. If not, see . -""" A simple changelog formater that consume merged commit message and produce nice pre-formated changelogs +"""A simple changelog formater that consume merged commit message and produce nice pre-formated changelogs. """ @@ -20,35 +20,60 @@ from abc import ABC def ChangeLogFormaterRecordType(Klass: type) -> type: - """decorator helper function to register interface implementation""" + """Decorator helper function to register interface implementation in factory + Args: + Klass: class to register in the factory + Returns: + untouched class""" ChangeLogFormater.ar_Klass.append(Klass) return Klass class ChangeLogFormater(ABC): - """the main changelog class that define nearly everythings. + """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... - Factory and base-object are mixed. + /// warning + Factory and base-objects are mixed in the same class. + /// """ ar_Klass: list[ChangeLogFormater] = [] ar_LinesResult: list[ChangeLogFormater] = [] - prefix: str = "" + 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 + + This class contain both formater and factory. + + /// warning + this class does not aim to be instantiated by user. + /// + Args: + scope: scope of the formater (tag) + ChangelogString: formater rendered title + """ self._scope = scope self._ChangelogString = ChangelogString.strip() def RenderLine(self): - """return a rendered line""" + """Get a rendered line + Returns: + the rendered line + """ return self._ChangelogString.strip() @classmethod def RenderLines(cls) -> str: - """render all lines""" + """Render all lines + Returns: + the rendered lines + """ changelog_category: str = "" lines = cls.GetLines() if len(lines) > 0: @@ -61,24 +86,41 @@ class ChangeLogFormater(ABC): return changelog_category def GetScope(self) -> str: - """return the current scope (category)""" + """Return the current scope (category) + Returns: + the current scope + """ return self._scope if self._scope is not None else "" @classmethod def Clear(cls) -> None: - """clear internal memory""" + """Clear internal memory""" ChangeLogFormater.ar_LinesResult = [] @classmethod - def CheckLine(cls, content: str) -> bool: - """check if a line is in the current scope (lazy identification)""" + 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: (): + Args: + content: line to parse + Returns: + match object + """ regex = re.compile(r"^(?:-\s+)?(?:{0})(?:\((.*)\))?(?::)(?:\s*)([^\s].+)".format(cls.prefix)) _match = regex.match(content) return _match @classmethod def CheckLine_keywords(cls, content: str) -> bool: - """check if a line is in the current scope (deeper in-word identification)""" + """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. + Args: + content: line to parse + Returns: + True if a keyword has matched + """ keyword_list = cls.keywords for _keyword in keyword_list: if (_keyword != "") and re.search(_keyword, content): @@ -87,7 +129,14 @@ class ChangeLogFormater(ABC): @classmethod def FactoryProcessLineMain(cls, RawChangelogLine: str) -> ChangeLogFormater: - """Process a line and look for identified ones""" + """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 + Args: + RawChangelogLine: line to parse + Returns: + a corresponding ChangeLogFormater_XXX() object, or a ChangeLogFormater_others() + """ for Klass in sorted(ChangeLogFormater.ar_Klass, key=lambda x: x.priority): content = Klass.CheckLine(RawChangelogLine) if content is not None: @@ -96,7 +145,14 @@ class ChangeLogFormater(ABC): @classmethod def FactoryProcessLineSecond(cls, RawChangelogLine: str) -> ChangeLogFormater: - """Process a line and look for non-identified ones""" + """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 + Args: + RawChangelogLine: line to parse + Returns: + a corresponding ChangeLogFormater_XXX() object, or a ChangeLogFormater_others() + """ for Klass in sorted(ChangeLogFormater.ar_Klass, key=lambda x: x.priority, reverse=True): if Klass.CheckLine_keywords(RawChangelogLine): return Klass(None, RawChangelogLine) @@ -105,16 +161,29 @@ class ChangeLogFormater(ABC): @classmethod def FactoryProcessFullChangelog(cls, RawChangelogMessage: str) -> list[ChangeLogFormater]: - """Process all input lines""" + """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. + 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 + """ LinesResult = [] Lines2ndRound = [] for line in RawChangelogMessage.split("\n"): - if line.strip() != "": + lineWordsCount = len(line.split()) + if (lineWordsCount > 1) and (not re.match(cls.checkCommentPattern, line)): res = cls.FactoryProcessLineMain(line) - if res is not ChangeLogFormater_others: + if type(res) is not ChangeLogFormater_others: LinesResult.append(res) - else: + elif lineWordsCount > 2: Lines2ndRound.append(line) for line in Lines2ndRound: @@ -125,76 +194,93 @@ class ChangeLogFormater(ABC): @classmethod def GetLinesOfType(cls, Klass: type) -> list[ChangeLogFormater]: - """retrieve all lines of specified type""" + """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""" + """Retrieve all lines for the current formater + Returns: + a list of ChangeLogFormater_XXX() object + """ return ChangeLogFormater.GetLinesOfType(cls) @classmethod - def RenderFullChangelog(cls) -> str: - """render the main changelog""" + def RenderFullChangelog(cls, include_unknown: bool = False) -> str: + """Render the main changelog + Args: + include_unknown: includes unknown lines in an Unknown category + Returns: + 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): + continue full_changelog = full_changelog + Klass.RenderLines() return full_changelog # to avoid writing class, they are initialized with the following structure: -# creating category classes: '': (priority, ['',...], '
') +# creating category classes: '': ( priority, ['',...], +# '
' +# ) +# +# => priority is both for ordering categories in final changelog +# and parsing commit to extract messages +# for RecordType, Config in { - "break": ( - 20, - [], - ":rotating_light: Breaking changes :rotating_light::", - ), - "feat": (20, ["feat", "new", "create", "add"], "Features :sparkles::"), - "fix": (10, ["issue", "problem"], "Fixes :wrench::"), - "security": (20, ["safe", "leak"], "Security :shield::"), - "chore": ( - 20, - ["task", "refactor", "build", "better", "improve"], - "Chore :building_construction::", - ), - "perf": ( - 0, - [ - "fast", - ], - "Performance Enhancements :rocket::", - ), - "wip": ( - 0, - [ - "temp", - ], - "Work in progress changes :construction::", - ), - "docs": ( - 0, - [ - "doc", - ], - "Documentations :book::", - ), - "style": ( - 5, - [ - "beautify", - ], - "Style :art::", - ), - "refactor": (0, [], "Refactorings :recycle::"), - "ci": (0, ["jenkins", "git"], "Continuous Integration :cyclone::"), - "test": (15, ["unittest", "check", r"^(?:\s)*test(?:\s)*$"], "Testings :vertical_traffic_light::"), - "build": (0, ["compile", "version"], "Builds :package:"), + # fmt: off + "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": ( 20, ["task", "refactor", "build", "better", "improve"], + "Chore :building_construction::", + ), + "perf": ( 0, ["fast", ], + "Performance Enhancements :rocket::", + ), + "wip": ( 0, ["temp", ], + "Work in progress changes :construction::", + ), + "docs": ( 0, [ "doc", ], + "Documentations :book::", + ), + "style": ( 5, ["beautify", ], + "Style :art::", + ), + "refactor": ( 0, [], + "Refactorings :recycle::" + ), + "ci": ( 0, ["jenkins", "git"], + "Continuous Integration :cyclone::" + ), + "test": ( -5, ["unittest", "check", r"^(?:\s)*test(?:\s)*$"], + "Testings :vertical_traffic_light::" + ), + "build": ( 0, ["compile", "version"], + "Builds :package:" + ), + # fmt: on }.items(): # then we instantiate all of them - name = f"ChangeLogFormater_{RecordType}" - tmp = globals()[name] = type( - name, + _name = f"ChangeLogFormater_{RecordType}" + _tmp = globals()[_name] = type( + _name, (ChangeLogFormater,), { "prefix": RecordType, @@ -203,12 +289,12 @@ for RecordType, Config in { "priority": Config[0], }, ) - ChangeLogFormater.ar_Klass.append(tmp) + ChangeLogFormater.ar_Klass.append(_tmp) @ChangeLogFormaterRecordType class ChangeLogFormater_revert(ChangeLogFormater): - """revert scope formater""" + """Revert scope formater""" prefix: str = "revert" title: str = "Reverts :back::" @@ -216,12 +302,16 @@ class ChangeLogFormater_revert(ChangeLogFormater): priority: int = 0 def RenderLine(self) -> str: + """an overloaded RenderLine implementation that adds surrounding '~~' + Returns: + the rendered pattern + """ return "~~" + super().RenderLine() + "~~" @ChangeLogFormaterRecordType class ChangeLogFormater_others(ChangeLogFormater): - """others / unknown scope formater""" + """Others / unknown scope formater""" prefix: str = "other" title: str = "Others :question::" diff --git a/test/test_changelogfactory.py b/test/test_changelogfactory.py index a9e1301..fcab973 100644 --- a/test/test_changelogfactory.py +++ b/test/test_changelogfactory.py @@ -18,82 +18,122 @@ class Testtest_module(unittest.TestCase): 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" + + pychangelogfactory.ChangeLogFormater.FactoryProcessFullChangelog(raw) + changelog = pychangelogfactory.ChangeLogFormater.RenderFullChangelog() + + self.assertIn("testbreak", changelog) + self.assertNotIn("testdoc", changelog) + self.assertNotIn("teststyle", changelog) + self.assertNotIn("testtest", changelog) + + def test_simplegeneration_ignored(self): + raw = "break: testbreak" + "\n" + "#docs: testdoc" + "\n" + "#style: teststyle" + "\n" + "//test: testtest" + + pychangelogfactory.ChangeLogFormater.FactoryProcessFullChangelog(raw) + changelog = pychangelogfactory.ChangeLogFormater.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() self.assertIn("testbreak", changelog[1]) - self.assertIn("testtest", changelog[3]) - self.assertIn("teststyle", changelog[5]) - self.assertIn("testdoc", changelog[7]) + 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" - self.simplegeneration(raw, ["testbreak", "testdoc", "teststyle"]) def test_simplegeneration_breaking(self): self.simplegeneration("break: teststring", ["teststring"]) - self.simplegeneration("test break", ["test break"]) + self.simplegeneration("test break dummy1 dummy2", ["test break"]) def test_simplegeneration_features(self): self.simplegeneration("feat: teststring", ["teststring"]) - self.simplegeneration("test feat", ["test feat"]) - self.simplegeneration("test new", ["test new"]) - self.simplegeneration("test create", ["test create"]) - self.simplegeneration("test add", ["test add"]) + self.simplegeneration("test feat dummy1 dummy2", ["test feat"]) + self.simplegeneration("test new dummy1 dummy2", ["test new"]) + self.simplegeneration("test create dummy1 dummy2", ["test create"]) + self.simplegeneration("test add dummy1 dummy2", ["test add"]) def test_simplegeneration_fix(self): self.simplegeneration("fix: teststring", ["teststring"]) - self.simplegeneration("test fix", ["test fix"]) - self.simplegeneration("test issue", ["test issue"]) - self.simplegeneration("test problem", ["test problem"]) + self.simplegeneration("test fix dummy1 dummy2", ["test fix"]) + self.simplegeneration("test issue dummy1 dummy2", ["test issue"]) + self.simplegeneration("test problem dummy1 dummy2", ["test problem"]) def test_simplegeneration_security(self): self.simplegeneration("security: teststring", ["teststring"]) - self.simplegeneration("test safe", ["test safe"]) - self.simplegeneration("test leak", ["test leak"]) + self.simplegeneration("test safe dummy1 dummy2", ["test safe"]) + self.simplegeneration("test leak dummy1 dummy2", ["test leak"]) - def test_simplegeneration_task(self): - self.simplegeneration("task: teststring", ["teststring"]) - self.simplegeneration("test refactor", ["test refactor"]) - self.simplegeneration("test build", ["test build"]) - self.simplegeneration("test better", ["test better"]) - self.simplegeneration("test improve", ["test improve"]) + def test_simplegeneration_chore(self): + self.simplegeneration("chore: teststring", ["teststring"]) + self.simplegeneration("chore refactor dummy1 dummy2", ["chore refactor"]) + self.simplegeneration("chore build dummy1 dummy2", ["chore build"]) + self.simplegeneration("chore better dummy1 dummy2", ["chore better"]) + self.simplegeneration("chore improve dummy1 dummy2", ["chore improve"]) def test_simplegeneration_perf(self): self.simplegeneration("perf: teststring", ["teststring"]) - self.simplegeneration("test fast", ["test fast"]) + self.simplegeneration("test fast dummy1 dummy2", ["test fast"]) def test_simplegeneration_wip(self): self.simplegeneration("wip: teststring", ["teststring"]) - self.simplegeneration("test temp", ["test temp"]) + self.simplegeneration("test temp dummy1 dummy2", ["test temp"]) def test_simplegeneration_docs(self): self.simplegeneration("docs: teststring", ["teststring"]) - self.simplegeneration("test doc", ["test doc"]) + self.simplegeneration("test doc dummy1 dummy2", ["test doc"]) def test_simplegeneration_style(self): self.simplegeneration("style: teststring", ["teststring"]) - self.simplegeneration("test beautify", ["test beautify"]) + self.simplegeneration("test beautify dummy1 dummy2", ["test beautify"]) def test_simplegeneration_refactor(self): self.simplegeneration("refactor: teststring", ["teststring"]) def test_simplegeneration_ci(self): self.simplegeneration("ci: teststring", ["teststring"]) - self.simplegeneration("test jenkins", ["test jenkins"]) - self.simplegeneration("test git", ["test git"]) + self.simplegeneration("test jenkins dummy1 dummy2", ["test jenkins"]) + self.simplegeneration("test git dummy1 dummy2", ["test git"]) def test_simplegeneration_test(self): self.simplegeneration("test: teststring", ["teststring"]) - self.simplegeneration("test unittest", ["test unittest"]) - self.simplegeneration("test check", ["test check"]) + self.simplegeneration("test unittest dummy1 dummy2", ["test unittest"]) + self.simplegeneration("test check dummy1 dummy2", ["test check"]) def test_simplegeneration_build(self): self.simplegeneration("build: teststring", ["teststring"]) - self.simplegeneration("test compile", ["test compile"]) - self.simplegeneration("test version", ["test version"]) + self.simplegeneration("test compile dummy1 dummy2", ["test compile"]) + self.simplegeneration("test version dummy1 dummy2", ["test version"]) def test_simplegeneration_revert(self): self.simplegeneration("revert: teststring", ["~~teststring~~"]) + + 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() + + 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" + ) + + self.assertEqual(changelog, expected_formated)