diff --git a/helpers/__init__.py b/helpers/__init__.py index 725cc2e..d2eaf8e 100644 --- a/helpers/__init__.py +++ b/helpers/__init__.py @@ -4,4 +4,4 @@ # 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 . \ No newline at end of file +# work. If not, see . diff --git a/helpers/__main__.py b/helpers/__main__.py index 8539d81..51d805c 100644 --- a/helpers/__main__.py +++ b/helpers/__main__.py @@ -16,89 +16,132 @@ import os import logging import sys -if __package__=="helpers": +if __package__ == "helpers": # when calling the module from: > python -m helpers - from .types_check import types_check - from .quality_check import quality_check - from .unit_test import unit_test - from .doc_gen import doc_gen - from .changelog_gen import changelog_gen -else: + from .types_check import types_check + from .quality_check import quality_check + from .unit_test import unit_test + from .doc_gen import doc_gen + from .changelog_gen import changelog_gen +else: # when calling the __main__.py file (from IDE) - from helpers.types_check import types_check - from helpers.quality_check import quality_check - from helpers.unit_test import unit_test - from helpers.doc_gen import doc_gen - from helpers.changelog_gen import changelog_gen - + from helpers.types_check import types_check + from helpers.quality_check import quality_check + from helpers.unit_test import unit_test + from helpers.doc_gen import doc_gen + from helpers.changelog_gen import changelog_gen + logging.getLogger().setLevel(logging.INFO) if __name__ == "__main__": - project_rootdir_path=Path(__file__).parent.parent.absolute() + project_rootdir_path = Path(__file__).parent.parent.absolute() with open(project_rootdir_path / "pyproject.toml", mode="rb") as fp: pyproject = tomli.load(fp) - - parser = argparse.ArgumentParser( prog = 'continuous-integration-helper', - description = 'A tiny set of scripts to help continous integration on python') - - parser.add_argument('-tc', '--type-check', dest='typecheck', action='store_true', help='enable static typing check') - - parser.add_argument('-ut', '--unit-test', dest='unittest', action='store_true', help='enable unit-test') - parser.add_argument('-cc', '--coverage-check', dest='coveragecheck', action='store_true', help='enable unit-test coverage check (requires unit-test)') - parser.add_argument('-qc', '--quality-check', dest='qualitycheck', action='store_true', help='enable code quality check') - - parser.add_argument('-dg', '--doc-gen', dest='docgen', action='store_true', help='enable documentation generation using MkDoc') - parser.add_argument('-pdf', '--doc-gen-pdf', dest='docgenpdf', action='store_true', help='enable pdf documentation export (requires doc-gen)') - - parser.add_argument('-clg', '--changelog-gen', dest='changeloggen', action='store_true', help='enable changelog generation') - + parser = argparse.ArgumentParser( + prog="continuous-integration-helper", + description="A tiny set of scripts to help continous integration on python", + ) + + parser.add_argument( + "-tc", + "--type-check", + dest="typecheck", + action="store_true", + help="enable static typing check", + ) + + parser.add_argument( + "-ut", + "--unit-test", + dest="unittest", + action="store_true", + help="enable unit-test", + ) + parser.add_argument( + "-cc", + "--coverage-check", + dest="coveragecheck", + action="store_true", + help="enable unit-test coverage check (requires unit-test)", + ) + + parser.add_argument( + "-qc", + "--quality-check", + dest="qualitycheck", + action="store_true", + help="enable code quality check", + ) + + parser.add_argument( + "-dg", + "--doc-gen", + dest="docgen", + action="store_true", + help="enable documentation generation using MkDoc", + ) + parser.add_argument( + "-pdf", + "--doc-gen-pdf", + dest="docgenpdf", + action="store_true", + help="enable pdf documentation export (requires doc-gen)", + ) + + parser.add_argument( + "-clg", + "--changelog-gen", + dest="changeloggen", + action="store_true", + help="enable changelog generation", + ) + args = parser.parse_args() - + ################################## # Dev / Debug forced toogles # - #-------------------------------- + # -------------------------------- # - #args.typecheck = True - #args.qualitycheck = True - #args.unittest = True - #args.coveragecheck = True - #args.docgen = True - #args.docgenpdf = True - #args.changeloggen = True - + # args.typecheck = True + # args.qualitycheck = True + # args.unittest = True + # args.coveragecheck = True + # args.docgen = True + # args.docgenpdf = True + # args.changeloggen = True + helpers = [] - if args.typecheck == True: + if args.typecheck == True: helpers.append(types_check) - - if args.unittest == True: + + if args.unittest == True: helpers.append(unit_test) - - if args.coveragecheck == True: + + if args.coveragecheck == True: if args.unittest == True: unit_test.enable_coverage_check = True else: raise RuntimeError("unit-test is required to enable coverage-check") - - if args.qualitycheck == True: + + if args.qualitycheck == True: helpers.append(quality_check) - - if args.docgen == True: + + if args.docgen == True: helpers.append(doc_gen) - - if args.docgenpdf==True: + + if args.docgenpdf == True: if args.docgen == True: doc_gen.enable_gen_pdf = True else: raise RuntimeError("doc-gen is required to enable doc-gen-pdf") - - if args.changeloggen == True: + + if args.changeloggen == True: helpers.append(changelog_gen) - + for helper in helpers: - helper.set_context(project_rootdir_path,pyproject) + helper.set_context(project_rootdir_path, pyproject) helper.reset_result_dir() helper.do_job() - diff --git a/helpers/changelog_gen.py b/helpers/changelog_gen.py index a0b5734..f4a15cb 100644 --- a/helpers/changelog_gen.py +++ b/helpers/changelog_gen.py @@ -9,16 +9,15 @@ from __future__ import annotations from typing import TYPE_CHECKING -#from pathlib import Path -#import os -#import datetime +# from pathlib import Path +# import os +# import datetime from .helper_base import helper_base -class changelog_gen(helper_base): - - @classmethod - def do_job(cls): - pass \ No newline at end of file +class changelog_gen(helper_base): + @classmethod + def do_job(cls): + pass diff --git a/helpers/doc_gen.py b/helpers/doc_gen.py index 05dcb38..16774b1 100644 --- a/helpers/doc_gen.py +++ b/helpers/doc_gen.py @@ -17,6 +17,7 @@ from pathlib import Path from distutils.dir_util import copy_tree import yaml + try: from yaml import CLoader as Loader, CDumper as Dumper except ImportError: @@ -24,35 +25,44 @@ except ImportError: from .helper_base import helper_withresults_base -class doc_gen(helper_withresults_base): + +class doc_gen(helper_withresults_base): enable_gen_pdf: bool = False - - @classmethod - def do_job(cls): + + @classmethod + def do_job(cls): print(cls.project_rootdir_path) print() - + # create doc root dir - doc_path = cls.project_rootdir_path/"docs" + doc_path = cls.project_rootdir_path / "docs" cls._reset_dir(doc_path) - - site_path = cls.get_result_dir()/"site" + + site_path = cls.get_result_dir() / "site" cls._reset_dir(site_path) - + # copy files from main project dir - shutil.copyfile(str(cls.project_rootdir_path/"README.md"),str(doc_path/"README.md")) - shutil.copyfile(str(cls.project_rootdir_path/"LICENSE.md"),str(doc_path/"LICENSE.md")) - + shutil.copyfile( + str(cls.project_rootdir_path / "README.md"), str(doc_path / "README.md") + ) + shutil.copyfile( + str(cls.project_rootdir_path / "LICENSE.md"), str(doc_path / "LICENSE.md") + ) + # copy files from static-doc dir - copy_tree(str(cls.project_rootdir_path/"docs-static"),str(doc_path)) - + copy_tree(str(cls.project_rootdir_path / "docs-static"), str(doc_path)) + # generating API doc + nav from python docstrings reference_path = doc_path / "reference" cls._reset_dir(reference_path) - + for path in sorted((cls.project_rootdir_path / "src").rglob("*.py")): - module_path = path.relative_to(cls.project_rootdir_path/ "src").with_suffix("") - doc_path = path.relative_to(cls.project_rootdir_path/ "src").with_suffix(".md") + module_path = path.relative_to( + cls.project_rootdir_path / "src" + ).with_suffix("") + doc_path = path.relative_to(cls.project_rootdir_path / "src").with_suffix( + ".md" + ) full_doc_path = Path(reference_path, doc_path) parts = list(module_path.parts) @@ -61,43 +71,59 @@ class doc_gen(helper_withresults_base): parts = parts[:-1] elif parts[-1] == "__main__": continue - + cls._reset_dir(os.path.dirname(full_doc_path)) with open(full_doc_path, "w+") as fd: - identifier = "src."+".".join(parts) + identifier = "src." + ".".join(parts) print("::: " + identifier, file=fd) - - - cmdopts = [f"{sys.executable}","-m","mkdocs","-v","build","--site-dir",str(site_path),"--clean"] - + + cmdopts = [ + f"{sys.executable}", + "-m", + "mkdocs", + "-v", + "build", + "--site-dir", + str(site_path), + "--clean", + ] + # 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 + # => 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 = None + with open(cls.project_rootdir_path / "mkdocs.yml", "r") as mkdocsCfgFile: mkdocsCfg = yaml.load(mkdocsCfgFile, Loader=yaml.SafeLoader) - - if cls.enable_gen_pdf==True: - mkdocsCfg['plugins'].append({ 'with-pdf':{ - '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') - }}) + + if cls.enable_gen_pdf == True: + mkdocsCfg["plugins"].append( + { + "with-pdf": { + "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)) - - - res=cls.run_cmd(cmdopts) + 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 + ) + ) + + res = cls.run_cmd(cmdopts) print(res.decode()) - print(' !! done') - + print(" !! done") diff --git a/helpers/helper_base.py b/helpers/helper_base.py index 74cf0e8..be2583b 100644 --- a/helpers/helper_base.py +++ b/helpers/helper_base.py @@ -9,64 +9,76 @@ from __future__ import annotations from typing import TYPE_CHECKING -from abc import ABC,abstractmethod +from abc import ABC, abstractmethod import os from pathlib import Path import subprocess if TYPE_CHECKING: # Only imports the below statements during type checking - from typing import Union + from typing import Union + class helper_base(ABC): - project_rootdir_path: Union[Path,None] = None - pyproject: Union[dict,None] = None - current_dir: Union[Path,None] = None - - @classmethod - def set_context(cls,project_rootdir_path:Path, pyproject:dict): - cls.project_rootdir_path = project_rootdir_path - cls.pyproject = pyproject - cls.current_dir = Path(__file__).parent.absolute() - - @classmethod + project_rootdir_path: Union[Path, None] = None + pyproject: Union[dict, None] = None + current_dir: Union[Path, None] = None + + @classmethod + def set_context(cls, project_rootdir_path: Path, pyproject: dict): + cls.project_rootdir_path = project_rootdir_path + cls.pyproject = pyproject + cls.current_dir = Path(__file__).parent.absolute() + + @classmethod def get_result_dir(cls): return None - - @staticmethod - def _reset_dir(dirpath:Path): + + @staticmethod + def _reset_dir(dirpath: Path): dirpath = Path(dirpath) if not os.path.exists(dirpath): os.makedirs(dirpath) - [f.unlink() for f in Path(dirpath).glob("*") if f.is_file()] - - @classmethod + [f.unlink() for f in Path(dirpath).glob("*") if f.is_file()] + + @classmethod def reset_result_dir(cls): result_dir = cls.get_result_dir() if result_dir != None: cls._reset_dir(result_dir) - - @classmethod + + @classmethod @abstractmethod - def do_job(cls): + def do_job(cls): raise NotImplementedError() - @classmethod + @classmethod def run_cmd_(cls, cmdarray): - process = subprocess.run(cmdarray, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True, check=True) + process = subprocess.run( + cmdarray, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + shell=True, + check=True, + ) return process.stdout - - @classmethod + + @classmethod def run_cmd(cls, cmdarray): p = subprocess.run(cmdarray, capture_output=True) print(p.stdout) print(p.stderr) return p.stdout - + + class helper_withresults_base(helper_base): - helper_results_dir: Union[Path,None] = None - - @classmethod + helper_results_dir: Union[Path, None] = None + + @classmethod def get_result_dir(cls): - if cls.helper_results_dir==None: - cls.helper_results_dir=cls.__name__ - return Path(__file__).parent.parent.absolute() / "helpers-results" / cls.helper_results_dir \ No newline at end of file + if cls.helper_results_dir == None: + cls.helper_results_dir = cls.__name__ + return ( + Path(__file__).parent.parent.absolute() + / "helpers-results" + / cls.helper_results_dir + ) diff --git a/helpers/quality_check.py b/helpers/quality_check.py index 7616d5f..7a36a87 100644 --- a/helpers/quality_check.py +++ b/helpers/quality_check.py @@ -25,197 +25,289 @@ import pylint_json2html from .helper_base import helper_withresults_base + class PyLintMetricNotFound(Warning): pass -class quality_check(helper_withresults_base): - PylintMessageList=dict() - - @classmethod + +class quality_check(helper_withresults_base): + PylintMessageList = dict() + + @classmethod def GetPylintMessageList(cls): - Messagelist=dict() - regex = r"^:([a-zA-Z-]+) \(([^\)]+)\)" - for line in cls.run_cmd([sys.executable,"-m","pylint","--list-msgs"]).splitlines(): - if res:=re.search(regex,line.decode()): - Messagelist[res.group(1)]=res.group(2) + Messagelist = dict() + regex = r"^:([a-zA-Z-]+) \(([^\)]+)\)" + for line in cls.run_cmd( + [sys.executable, "-m", "pylint", "--list-msgs"] + ).splitlines(): + if res := re.search(regex, line.decode()): + Messagelist[res.group(1)] = res.group(2) cls.PylintMessageList = Messagelist @staticmethod - def TryExtractPYReportMetric(line: str,tag: str): - regex=f"^(?:\|{tag}\s*\|)(\d+)(?=\s*|)" - if res:=re.search(regex,line): + def TryExtractPYReportMetric(line: str, tag: str): + regex = f"^(?:\|{tag}\s*\|)(\d+)(?=\s*|)" + if res := re.search(regex, line): return float(res.group(1)) raise PyLintMetricNotFound() - @classmethod - def do_job(cls): + @classmethod + def do_job(cls): print("checking code quality ...") cls.GetPylintMessageList() - - RES_all=dict() + + RES_all = dict() with StringIO() as StdOutput: - JsonContent="" + JsonContent = "" with redirect_stdout(StdOutput): - pylint_Run(['--output-format=json,parseable', - '--disable=invalid-name', - '--ignore=_version.py', - '--reports=y', - '--score=yes', - '--max-line-length=140', - 'src.' + cls.pyproject['project']['name']], exit=False) - - with open(cls.get_result_dir()/"report.json","w+", encoding='utf-8') as Outfile: + pylint_Run( + [ + "--output-format=json,parseable", + "--disable=invalid-name", + "--ignore=_version.py", + "--reports=y", + "--score=yes", + "--max-line-length=140", + "src." + cls.pyproject["project"]["name"], + ], + exit=False, + ) + + with open( + cls.get_result_dir() / "report.json", "w+", encoding="utf-8" + ) as Outfile: # hacky way of exctracting json + having overall score... class TScanState(Enum): - TEXT_REPORT = 1 - JSON_REPORT = 2 - OTHER_REPORT_START = 3 - OTHER_REPORT_STATISTICS = 4 - OTHER_REPORT_METRICS = 5 - OTHER_REPORT_DUPLICATION = 6 - OTHER_REPORT_MESSAGES_CAT = 7 - OTHER_REPORT_MESSAGES = 8 - OTHER_REPORT_END = 99 - - RES_all['Statistics'] = dict() - RES_all['RawMetrics'] = dict() - RES_all['RawMetricsPercent'] = dict() - RES_all['Duplication'] = dict() - RES_all['MessagesCat'] = dict() - RES_all['Messages'] = dict() - RES_all['GlobalScore'] = -999 - RES_all['NbAnalysedStatments'] = -999 - RES_all['NbAnalysedLines'] = -999 - + TEXT_REPORT = 1 + JSON_REPORT = 2 + OTHER_REPORT_START = 3 + OTHER_REPORT_STATISTICS = 4 + OTHER_REPORT_METRICS = 5 + OTHER_REPORT_DUPLICATION = 6 + OTHER_REPORT_MESSAGES_CAT = 7 + OTHER_REPORT_MESSAGES = 8 + OTHER_REPORT_END = 99 + + RES_all["Statistics"] = dict() + RES_all["RawMetrics"] = dict() + RES_all["RawMetricsPercent"] = dict() + RES_all["Duplication"] = dict() + RES_all["MessagesCat"] = dict() + RES_all["Messages"] = dict() + RES_all["GlobalScore"] = -999 + RES_all["NbAnalysedStatments"] = -999 + RES_all["NbAnalysedLines"] = -999 + ScanState = TScanState.TEXT_REPORT - for line in StdOutput.getvalue().split('\n'): - print(line) - if ScanState == TScanState.TEXT_REPORT: + for line in StdOutput.getvalue().split("\n"): + print(line) + if ScanState == TScanState.TEXT_REPORT: # ignoring this part, we need json - if line=='[': - JsonContent+=line + if line == "[": + JsonContent += line ScanState = TScanState.JSON_REPORT - elif line=='[]': - JsonContent+=line + elif line == "[]": + JsonContent += line ScanState = TScanState.OTHER_REPORT_START - - elif ScanState == TScanState.JSON_REPORT: - JsonContent+=line - if line==']': + + elif ScanState == TScanState.JSON_REPORT: + JsonContent += line + if line == "]": ScanState = TScanState.OTHER_REPORT_START - - elif ScanState == TScanState.OTHER_REPORT_START: - if res:=re.search(r"^(\d+)(?= statements analysed.)",line): - RES_all['NbAnalysedStatments'] = float(res.group(1)) + + elif ScanState == TScanState.OTHER_REPORT_START: + if res := re.search(r"^(\d+)(?= statements analysed.)", line): + RES_all["NbAnalysedStatments"] = float(res.group(1)) if line == "Statistics by type": ScanState = TScanState.OTHER_REPORT_STATISTICS - - elif ScanState == TScanState.OTHER_REPORT_STATISTICS: - if res:=re.search(r"^(\d+)(?= lines have been analyzed)",line): - RES_all['NbAnalysedLines' ]= float(res.group(1)) + + elif ScanState == TScanState.OTHER_REPORT_STATISTICS: + if res := re.search( + r"^(\d+)(?= lines have been analyzed)", line + ): + RES_all["NbAnalysedLines"] = float(res.group(1)) elif line == "Raw metrics": ScanState = TScanState.OTHER_REPORT_METRICS else: - with suppress(PyLintMetricNotFound): RES_all['Statistics']['module'] = cls.TryExtractPYReportMetric(line,"module") - with suppress(PyLintMetricNotFound): RES_all['Statistics']['class'] = cls.TryExtractPYReportMetric(line,"class") - with suppress(PyLintMetricNotFound): RES_all['Statistics']['method'] = cls.TryExtractPYReportMetric(line,"method") - with suppress(PyLintMetricNotFound): RES_all['Statistics']['function'] = cls.TryExtractPYReportMetric(line,"function") - - elif ScanState == TScanState.OTHER_REPORT_METRICS: + with suppress(PyLintMetricNotFound): + RES_all["Statistics"][ + "module" + ] = cls.TryExtractPYReportMetric(line, "module") + with suppress(PyLintMetricNotFound): + RES_all["Statistics"][ + "class" + ] = cls.TryExtractPYReportMetric(line, "class") + with suppress(PyLintMetricNotFound): + RES_all["Statistics"][ + "method" + ] = cls.TryExtractPYReportMetric(line, "method") + with suppress(PyLintMetricNotFound): + RES_all["Statistics"][ + "function" + ] = cls.TryExtractPYReportMetric(line, "function") + + elif ScanState == TScanState.OTHER_REPORT_METRICS: if line == "Duplication": - RES_all['RawMetricsPercent']['code'] = RES_all['RawMetrics']['code'] / RES_all['NbAnalysedLines' ] - RES_all['RawMetricsPercent']['docstring'] = RES_all['RawMetrics']['docstring'] / RES_all['NbAnalysedLines' ] - RES_all['RawMetricsPercent']['comment'] = RES_all['RawMetrics']['comment'] / RES_all['NbAnalysedLines' ] - RES_all['RawMetricsPercent']['empty'] = RES_all['RawMetrics']['empty'] / RES_all['NbAnalysedLines' ] + RES_all["RawMetricsPercent"]["code"] = ( + RES_all["RawMetrics"]["code"] + / RES_all["NbAnalysedLines"] + ) + RES_all["RawMetricsPercent"]["docstring"] = ( + RES_all["RawMetrics"]["docstring"] + / RES_all["NbAnalysedLines"] + ) + RES_all["RawMetricsPercent"]["comment"] = ( + RES_all["RawMetrics"]["comment"] + / RES_all["NbAnalysedLines"] + ) + RES_all["RawMetricsPercent"]["empty"] = ( + RES_all["RawMetrics"]["empty"] + / RES_all["NbAnalysedLines"] + ) ScanState = TScanState.OTHER_REPORT_DUPLICATION else: - with suppress(PyLintMetricNotFound): RES_all['RawMetrics']['code'] = cls.TryExtractPYReportMetric(line,"code") - with suppress(PyLintMetricNotFound): RES_all['RawMetrics']['docstring'] = cls.TryExtractPYReportMetric(line,"docstring") - with suppress(PyLintMetricNotFound): RES_all['RawMetrics']['comment'] = cls.TryExtractPYReportMetric(line,"comment") - with suppress(PyLintMetricNotFound): RES_all['RawMetrics']['empty'] = cls.TryExtractPYReportMetric(line,"empty") - - elif ScanState == TScanState.OTHER_REPORT_DUPLICATION: + with suppress(PyLintMetricNotFound): + RES_all["RawMetrics"][ + "code" + ] = cls.TryExtractPYReportMetric(line, "code") + with suppress(PyLintMetricNotFound): + RES_all["RawMetrics"][ + "docstring" + ] = cls.TryExtractPYReportMetric(line, "docstring") + with suppress(PyLintMetricNotFound): + RES_all["RawMetrics"][ + "comment" + ] = cls.TryExtractPYReportMetric(line, "comment") + with suppress(PyLintMetricNotFound): + RES_all["RawMetrics"][ + "empty" + ] = cls.TryExtractPYReportMetric(line, "empty") + + elif ScanState == TScanState.OTHER_REPORT_DUPLICATION: if line == "Messages by category": ScanState = TScanState.OTHER_REPORT_MESSAGES_CAT else: - with suppress(PyLintMetricNotFound): RES_all['Duplication']['NbDupLines'] = cls.TryExtractPYReportMetric(line,"nb duplicated lines") - with suppress(PyLintMetricNotFound): RES_all['Duplication']['PersentDuplicatedLines'] = cls.TryExtractPYReportMetric(line,"percent duplicated lines") - - elif ScanState == TScanState.OTHER_REPORT_MESSAGES_CAT: + with suppress(PyLintMetricNotFound): + RES_all["Duplication"][ + "NbDupLines" + ] = cls.TryExtractPYReportMetric( + line, "nb duplicated lines" + ) + with suppress(PyLintMetricNotFound): + RES_all["Duplication"][ + "PersentDuplicatedLines" + ] = cls.TryExtractPYReportMetric( + line, "percent duplicated lines" + ) + + elif ScanState == TScanState.OTHER_REPORT_MESSAGES_CAT: if line == "Messages": ScanState = TScanState.OTHER_REPORT_MESSAGES else: - with suppress(PyLintMetricNotFound): RES_all['MessagesCat']['Convention'] = cls.TryExtractPYReportMetric(line,"convention") - with suppress(PyLintMetricNotFound): RES_all['MessagesCat']['Refactor'] = cls.TryExtractPYReportMetric(line,"refactor") - with suppress(PyLintMetricNotFound): RES_all['MessagesCat']['Warning'] = cls.TryExtractPYReportMetric(line,"warning") - with suppress(PyLintMetricNotFound): RES_all['MessagesCat']['Error'] = cls.TryExtractPYReportMetric(line,"error") - - elif ScanState == TScanState.OTHER_REPORT_MESSAGES: + with suppress(PyLintMetricNotFound): + RES_all["MessagesCat"][ + "Convention" + ] = cls.TryExtractPYReportMetric(line, "convention") + with suppress(PyLintMetricNotFound): + RES_all["MessagesCat"][ + "Refactor" + ] = cls.TryExtractPYReportMetric(line, "refactor") + with suppress(PyLintMetricNotFound): + RES_all["MessagesCat"][ + "Warning" + ] = cls.TryExtractPYReportMetric(line, "warning") + with suppress(PyLintMetricNotFound): + RES_all["MessagesCat"][ + "Error" + ] = cls.TryExtractPYReportMetric(line, "error") + + elif ScanState == TScanState.OTHER_REPORT_MESSAGES: # approx match because the number of '-' depend on screen width.. if line.startswith("--------"): ScanState = TScanState.OTHER_REPORT_END else: for PylintMessage in cls.PylintMessageList.keys(): - with suppress(PyLintMetricNotFound): RES_all['Messages'][PylintMessage] = cls.TryExtractPYReportMetric(line,PylintMessage) - - elif ScanState == TScanState.OTHER_REPORT_END: - if res:=re.search(r"(?<=Your code has been rated at )(\d+(?:\.\d+)?)/10",line): - RES_all['GlobalScore']=float(res.group(1)) - print(RES_all['GlobalScore']) + with suppress(PyLintMetricNotFound): + RES_all["Messages"][ + PylintMessage + ] = cls.TryExtractPYReportMetric( + line, PylintMessage + ) + + elif ScanState == TScanState.OTHER_REPORT_END: + if res := re.search( + r"(?<=Your code has been rated at )(\d+(?:\.\d+)?)/10", line + ): + RES_all["GlobalScore"] = float(res.group(1)) + print(RES_all["GlobalScore"]) else: raise RuntimeError("Invalid ScanState") Outfile.write(JsonContent) - - with open(cls.get_result_dir()/"metrics.json", 'w') as json_file: + + with open(cls.get_result_dir() / "metrics.json", "w") as json_file: json.dump(RES_all, json_file) - - # exporting all Data in one csv, unused atm because jenkins seems not able to select columns from csv an keep displaying all... + + # exporting all Data in one csv, unused atm because jenkins seems not able to select columns from csv an keep displaying all... # => to export a working full csv we need to a 'flat' dict (no more nested dict) RES_all_trim = copy.deepcopy(RES_all) del RES_all_trim["Messages"] - flat_RES_all=pandas.json_normalize(RES_all_trim, sep='_').to_dict(orient='records')[0] - - with open(cls.get_result_dir()/"metrics.csv", 'w', newline='') as csv_file: - writer = csv.DictWriter(csv_file,fieldnames=flat_RES_all.keys()) + flat_RES_all = pandas.json_normalize(RES_all_trim, sep="_").to_dict( + orient="records" + )[0] + + with open(cls.get_result_dir() / "metrics.csv", "w", newline="") as csv_file: + writer = csv.DictWriter(csv_file, fieldnames=flat_RES_all.keys()) writer.writeheader() writer.writerow(flat_RES_all) - + # splited csv exports for jenkins plots: RawMetricsPercent RES_all_percent = RES_all["RawMetricsPercent"] - with open(cls.get_result_dir()/"metrics_rawpercent.csv", 'w', newline='') as csv_file: - writer = csv.DictWriter(csv_file,fieldnames=RES_all_percent.keys()) + with open( + cls.get_result_dir() / "metrics_rawpercent.csv", "w", newline="" + ) as csv_file: + writer = csv.DictWriter(csv_file, fieldnames=RES_all_percent.keys()) writer.writeheader() writer.writerow(RES_all_percent) # splited csv exports for jenkins plots: Statistics + Duplication + NbAnalysedStatments + NbAnalysedLines RES_all_stats = copy.deepcopy(RES_all["Statistics"]) - RES_all_stats["NbDupLines"] = RES_all['Duplication']['NbDupLines'] - RES_all_stats["PersentDuplicatedLines"] = RES_all['Duplication']['PersentDuplicatedLines'] - RES_all_stats["NbAnalysedStatments"] = RES_all['NbAnalysedStatments' ] - RES_all_stats["NbAnalysedLines"] = RES_all['NbAnalysedLines' ] - with open(cls.get_result_dir()/"metrics_Statistics.csv", 'w', newline='') as csv_file: - writer = csv.DictWriter(csv_file,fieldnames=RES_all_stats.keys()) + RES_all_stats["NbDupLines"] = RES_all["Duplication"]["NbDupLines"] + RES_all_stats["PersentDuplicatedLines"] = RES_all["Duplication"][ + "PersentDuplicatedLines" + ] + RES_all_stats["NbAnalysedStatments"] = RES_all["NbAnalysedStatments"] + RES_all_stats["NbAnalysedLines"] = RES_all["NbAnalysedLines"] + with open( + cls.get_result_dir() / "metrics_Statistics.csv", "w", newline="" + ) as csv_file: + writer = csv.DictWriter(csv_file, fieldnames=RES_all_stats.keys()) writer.writeheader() writer.writerow(RES_all_stats) - + # splited csv exports for jenkins plots: Statistics + Duplication - RES_all_MessagesCat = RES_all['MessagesCat'] - with open(cls.get_result_dir()/"metrics_MessagesCat.csv", 'w', newline='') as csv_file: - writer = csv.DictWriter(csv_file,fieldnames=RES_all_MessagesCat.keys()) + RES_all_MessagesCat = RES_all["MessagesCat"] + with open( + cls.get_result_dir() / "metrics_MessagesCat.csv", "w", newline="" + ) as csv_file: + writer = csv.DictWriter(csv_file, fieldnames=RES_all_MessagesCat.keys()) writer.writeheader() writer.writerow(RES_all_MessagesCat) - + # splited csv exports for jenkins plots: GlobalScore - RES_GlobalScore = {'GlobalScore': RES_all['GlobalScore']} - with open(cls.get_result_dir()/"metrics_GlobalScore.csv", 'w', newline='') as csv_file: - writer = csv.DictWriter(csv_file,fieldnames=RES_GlobalScore.keys()) + RES_GlobalScore = {"GlobalScore": RES_all["GlobalScore"]} + with open( + cls.get_result_dir() / "metrics_GlobalScore.csv", "w", newline="" + ) as csv_file: + writer = csv.DictWriter(csv_file, fieldnames=RES_GlobalScore.keys()) writer.writeheader() writer.writerow(RES_GlobalScore) - + # converting the report using pylint_json2html (/!\ internal API, but as their is no leading '_' ...) - with open(cls.get_result_dir()/"report.html","w+", encoding='utf-8') as Outfile: + with open( + cls.get_result_dir() / "report.html", "w+", encoding="utf-8" + ) as Outfile: raw_data = json.loads(JsonContent) - report=pylint_json2html.Report( raw_data) + report = pylint_json2html.Report(raw_data) Outfile.write(report.render()) - + print("Done") diff --git a/helpers/types_check.py b/helpers/types_check.py index 4816e0c..6179581 100644 --- a/helpers/types_check.py +++ b/helpers/types_check.py @@ -16,36 +16,46 @@ from mypy import api from .helper_base import helper_withresults_base -class types_check(helper_withresults_base): +class types_check(helper_withresults_base): JUnitReportName = "junit.xml" - - @classmethod - def do_job(cls): + + @classmethod + def do_job(cls): print("checking code typing ...") - result = api.run([ # project path - "-m", - "src." + str(cls.pyproject['project']['name']), - # analysis configuration - "--ignore-missing-imports", - "--strict-equality", - # reports generation - "--cobertura-xml-report", str(cls.get_result_dir()), - "--html-report", str(cls.get_result_dir()), - "--linecount-report", str(cls.get_result_dir()), - "--linecoverage-report", str(cls.get_result_dir()), - "--lineprecision-report", str(cls.get_result_dir()), - "--txt-report", str(cls.get_result_dir()), - "--xml-report", str(cls.get_result_dir()), - "--junit-xml", str(cls.get_result_dir()) + "/" + cls.JUnitReportName - ]) - + result = api.run( + [ # project path + "-m", + "src." + str(cls.pyproject["project"]["name"]), + # analysis configuration + "--ignore-missing-imports", + "--strict-equality", + # reports generation + "--cobertura-xml-report", + str(cls.get_result_dir()), + "--html-report", + str(cls.get_result_dir()), + "--linecount-report", + str(cls.get_result_dir()), + "--linecoverage-report", + str(cls.get_result_dir()), + "--lineprecision-report", + str(cls.get_result_dir()), + "--txt-report", + str(cls.get_result_dir()), + "--xml-report", + str(cls.get_result_dir()), + "--junit-xml", + str(cls.get_result_dir()) + "/" + cls.JUnitReportName, + ] + ) + if result[0]: - print('\nType checking report:\n') + print("\nType checking report:\n") print(result[0]) # stdout - + if result[1]: - print('\nError report:\n') + print("\nError report:\n") print(result[1]) # stderr - - print('\nExit status:', result[2]) - print("Done") \ No newline at end of file + + print("\nExit status:", result[2]) + print("Done") diff --git a/helpers/unit_test.py b/helpers/unit_test.py index 571f2da..166fa5c 100644 --- a/helpers/unit_test.py +++ b/helpers/unit_test.py @@ -21,34 +21,41 @@ from junit2htmlreport import parser as junit2html_parser from .helper_base import helper_withresults_base -class unit_test(helper_withresults_base): - enable_coverage_check: bool = False - enable_xml_export: bool = True +class unit_test(helper_withresults_base): + enable_coverage_check: bool = False + enable_xml_export: bool = True enable_full_xml_export: bool = True - FullReportName: str = "full_report" - CoverageReportName: str = "test_coverage" - - @classmethod - def do_job(cls): - if cls.enable_coverage_check==True: + FullReportName: str = "full_report" + CoverageReportName: str = "test_coverage" + + @classmethod + def do_job(cls): + if cls.enable_coverage_check == True: import coverage - + # preparing unittest framework test_loader = unittest.TestLoader() - + if cls.enable_coverage_check == True: - #we start coverage now because module files discovery is part of the coverage measurement - CoverageReportPath = Path(str(cls.get_result_dir())+"_coverage") + # we start coverage now because module files discovery is part of the coverage measurement + CoverageReportPath = Path(str(cls.get_result_dir()) + "_coverage") cls._reset_dir(CoverageReportPath) - cov = coverage.Coverage(cover_pylib=False,branch=True,source_pkgs=["src."+cls.pyproject['project']['name']]) + cov = coverage.Coverage( + cover_pylib=False, + branch=True, + source_pkgs=["src." + cls.pyproject["project"]["name"]], + ) cov.start() - - package_tests = test_loader.discover(start_dir=str(cls.project_rootdir_path / "test"),top_level_dir=str(cls.project_rootdir_path / "test")) + + package_tests = test_loader.discover( + start_dir=str(cls.project_rootdir_path / "test"), + top_level_dir=str(cls.project_rootdir_path / "test"), + ) if cls.enable_xml_export: testRunner = xmlrunner.XMLTestRunner(output=str(str(cls.get_result_dir()))) else: testRunner = unittest.TextTestRunner() - + # running the test testRunner.run(package_tests) @@ -57,23 +64,33 @@ class unit_test(helper_withresults_base): cov.stop() cov.save() cov.html_report(directory=str(CoverageReportPath)) - cov.xml_report(outfile=(CoverageReportPath/f"{cls.CoverageReportName}.xml")) - + cov.xml_report( + outfile=(CoverageReportPath / f"{cls.CoverageReportName}.xml") + ) + # computing results (Only if xml available) if cls.enable_full_xml_export == True: print("Full reports generation...") - FullReportPath = Path(str(cls.get_result_dir())+"_full") + FullReportPath = Path(str(cls.get_result_dir()) + "_full") cls._reset_dir(FullReportPath) - + FullJUnitReport = JUnitXml() - for fname in [fname for fname in os.listdir(cls.get_result_dir()) if fname.endswith('.xml')]: - FullJUnitReport+=JUnitXml.fromfile(str(cls.get_result_dir() / fname)) - + for fname in [ + fname + for fname in os.listdir(cls.get_result_dir()) + if fname.endswith(".xml") + ]: + FullJUnitReport += JUnitXml.fromfile(str(cls.get_result_dir() / fname)) + current_datetime = datetime.datetime.utcnow().strftime("%Y%m%dT%H%M%SZ") full_report_base_name = f'{cls.pyproject["project"]["name"]}-{cls.FullReportName}-{current_datetime}' - FullJUnitReport.write(str(FullReportPath / f'{full_report_base_name}.xml')) - report = junit2html_parser.Junit(FullReportPath/ f'{full_report_base_name}.xml') + FullJUnitReport.write(str(FullReportPath / f"{full_report_base_name}.xml")) + report = junit2html_parser.Junit( + FullReportPath / f"{full_report_base_name}.xml" + ) html = report.html() - with open(FullReportPath/ f'{full_report_base_name}.html', "wb") as outfile: - outfile.write(html.encode('utf-8')) - print("Done") \ No newline at end of file + with open( + FullReportPath / f"{full_report_base_name}.html", "wb" + ) as outfile: + outfile.write(html.encode("utf-8")) + print("Done") diff --git a/src/pygitversionhelper/gitversionhelper.py b/src/pygitversionhelper/gitversionhelper.py index 6e8d48f..7b869ea 100644 --- a/src/pygitversionhelper/gitversionhelper.py +++ b/src/pygitversionhelper/gitversionhelper.py @@ -36,7 +36,10 @@ import logging from packaging.version import VERSION_PATTERN as packaging_VERSION_PATTERN -def _exec(cmd: str, root: str | os.PathLike | None = None, raw:bool = False) -> list[str]: + +def _exec( + cmd: str, root: str | os.PathLike | None = None, raw: bool = False +) -> list[str]: """ helper function to handle system cmd execution Args: @@ -44,14 +47,22 @@ def _exec(cmd: str, root: str | os.PathLike | None = None, raw:bool = False) -> root: root directory where the command need to be executed Returns: a list of command's return lines - + """ - p = subprocess.run(cmd, text=True, cwd=root, capture_output=True, check=False, timeout=2,shell=True) - if re.search("not a git repository",p.stderr): + p = subprocess.run( + cmd, + text=True, + cwd=root, + capture_output=True, + check=False, + timeout=2, + shell=True, + ) + if re.search("not a git repository", p.stderr): raise gitversionhelper.repository.notAGitRepository() - if re.search("fatal:",p.stderr): #pragma: nocover + if re.search("fatal:", p.stderr): # pragma: nocover raise gitversionhelper.unknownGITFatalError(p.stderr) - if int(p.returncode) < 0: #pragma: nocover + if int(p.returncode) < 0: # pragma: nocover raise gitversionhelper.unknownGITError(p.stderr) if raw: @@ -59,15 +70,18 @@ def _exec(cmd: str, root: str | os.PathLike | None = None, raw:bool = False) -> lines = p.stdout.splitlines() return [line.rstrip() for line in lines if line.rstrip()] + class gitversionhelperException(Exception): """ general Module Exception """ -class gitversionhelper: # pylint: disable=too-few-public-methods + +class gitversionhelper: # pylint: disable=too-few-public-methods """ main gitversionhelper class """ + class wrongArguments(gitversionhelperException): """ wrong argument generic exception @@ -87,6 +101,7 @@ class gitversionhelper: # pylint: disable=too-few-public-methods """ class containing methods focusing on repository """ + class repositoryException(gitversionhelperException): """ generic repository exeption @@ -110,12 +125,13 @@ class gitversionhelper: # pylint: disable=too-few-public-methods True if it is dirty """ return bool(_exec("git status --short")) + class commit: """ class containing methods focusing on commits """ - __OptDict = {"same_branch": "same_branch", - "merged_output":"merged_output"} + + __OptDict = {"same_branch": "same_branch", "merged_output": "merged_output"} class commitException(gitversionhelperException): """ @@ -128,7 +144,7 @@ class gitversionhelper: # pylint: disable=too-few-public-methods """ @classmethod - def getMessagesSinceTag(cls,tag:str,**kwargs) -> str: + def getMessagesSinceTag(cls, tag: str, **kwargs) -> str: """ retrieve a commits message history from repository from LastCommit to the given tag @@ -138,24 +154,32 @@ class gitversionhelper: # pylint: disable=too-few-public-methods Returns: the commit message """ - current_commit_id=cls.getLast(**kwargs) - tag_commit_id=cls.getFromTag(tag) + current_commit_id = cls.getLast(**kwargs) + tag_commit_id = cls.getFromTag(tag) - if ((cls.__OptDict["same_branch"] in kwargs) and (kwargs[cls.__OptDict["same_branch"]] is True)): - commits = _exec(f"git rev-list --first-parent --ancestry-path {tag_commit_id}..{current_commit_id}") + if (cls.__OptDict["same_branch"] in kwargs) and ( + kwargs[cls.__OptDict["same_branch"]] is True + ): + commits = _exec( + f"git rev-list --first-parent --ancestry-path {tag_commit_id}..{current_commit_id}" + ) else: - commits = _exec(f"git rev-list --ancestry-path {tag_commit_id}..{current_commit_id}") - result=[] + commits = _exec( + f"git rev-list --ancestry-path {tag_commit_id}..{current_commit_id}" + ) + result = [] for commit in commits: result.append(cls.getMessage(commit)) - if ((cls.__OptDict["merged_output"] in kwargs) and (kwargs[cls.__OptDict["merged_output"]] is True)): + if (cls.__OptDict["merged_output"] in kwargs) and ( + kwargs[cls.__OptDict["merged_output"]] is True + ): print("JOIN") return os.linesep.join(result) return result @classmethod - def getMessage(cls, commit_hash:str) -> str: + def getMessage(cls, commit_hash: str) -> str: """ retrieve a commit message from repository Args: @@ -164,14 +188,18 @@ class gitversionhelper: # pylint: disable=too-few-public-methods the commit message """ try: - res=_exec(f"git log -z --pretty=\"tformat:%B%-C()\" -n 1 {commit_hash}",None,True).rstrip('\x00') + res = _exec( + f'git log -z --pretty="tformat:%B%-C()" -n 1 {commit_hash}', + None, + True, + ).rstrip("\x00") except gitversionhelper.unknownGITFatalError as _e: raise cls.commitNotFound("no commit found in commit history") from _e - return res.replace('\r\n','\n').replace('\n','\r\n') + return res.replace("\r\n", "\n").replace("\n", "\r\n") @classmethod - def getFromTag(cls,tag:str) -> str: + def getFromTag(cls, tag: str) -> str: """ retrieve a commit from repository associated to a tag Args: @@ -180,15 +208,15 @@ class gitversionhelper: # pylint: disable=too-few-public-methods the commit Id """ try: - res=_exec(f"git rev-list -n 1 {tag}") + res = _exec(f"git rev-list -n 1 {tag}") except gitversionhelper.unknownGITFatalError as _e: raise cls.commitNotFound("no commit found in commit history") from _e - if len(res)==0: + if len(res) == 0: raise cls.commitNotFound("no commit found in commit history") return res[0] @classmethod - def getLast(cls,**kwargs) -> str: + def getLast(cls, **kwargs) -> str: """ retrieve last commit from repository Keyword Arguments: @@ -196,15 +224,21 @@ class gitversionhelper: # pylint: disable=too-few-public-methods Returns: the commit Id """ - if ((cls.__OptDict["same_branch"] in kwargs) and (kwargs[cls.__OptDict["same_branch"]] is True)): + if (cls.__OptDict["same_branch"] in kwargs) and ( + kwargs[cls.__OptDict["same_branch"]] is True + ): try: res = _exec("git rev-parse HEAD") except gitversionhelper.unknownGITFatalError as _e: - raise cls.commitNotFound("no commit found in commit history") from _e + raise cls.commitNotFound( + "no commit found in commit history" + ) from _e else: - res = _exec("git for-each-ref --sort=-committerdate refs/heads/ --count 1 --format=\"%(objectname)\"") + res = _exec( + 'git for-each-ref --sort=-committerdate refs/heads/ --count 1 --format="%(objectname)"' + ) - if len(res)==0: + if len(res) == 0: raise cls.commitNotFound("no commit found in commit history") return res[0] @@ -212,8 +246,17 @@ class gitversionhelper: # pylint: disable=too-few-public-methods """ class containing methods focusing on tags """ + __OptDict = {"same_branch": "same_branch"} - __validGitTagSort=["","v:refname","-v:refname","taggerdate","committerdate","-taggerdate","-committerdate"] + __validGitTagSort = [ + "", + "v:refname", + "-v:refname", + "taggerdate", + "committerdate", + "-taggerdate", + "-committerdate", + ] class tagException(gitversionhelperException): """ @@ -231,7 +274,7 @@ class gitversionhelper: # pylint: disable=too-few-public-methods """ @classmethod - def getTags(cls,sort:str = "taggerdate",**kwargs) -> list[str|None]: + def getTags(cls, sort: str = "taggerdate", **kwargs) -> list[str | None]: """ retrieve all tags from a repository Args: @@ -243,13 +286,19 @@ class gitversionhelper: # pylint: disable=too-few-public-methods if sort not in cls.__validGitTagSort: raise gitversionhelper.wrongArguments("sort option not in allowed list") - if ((cls.__OptDict["same_branch"] in kwargs) and (kwargs[cls.__OptDict["same_branch"]] is True)): + if (cls.__OptDict["same_branch"] in kwargs) and ( + kwargs[cls.__OptDict["same_branch"]] is True + ): currentBranch = _exec("git rev-parse --abbrev-ref HEAD") - return list(reversed(_exec(f"git tag --merged {currentBranch[0]} --sort={sort}"))) + return list( + reversed( + _exec(f"git tag --merged {currentBranch[0]} --sort={sort}") + ) + ) return list(reversed(_exec(f"git tag -l --sort={sort}"))) @classmethod - def getLastTag(cls,**kwargs) -> str | None: + def getLastTag(cls, **kwargs) -> str | None: """ retrieve the last tag from a repository Keyword Arguments: @@ -257,21 +306,23 @@ class gitversionhelper: # pylint: disable=too-few-public-methods Returns: the tag """ - if ((cls.__OptDict["same_branch"] in kwargs) and (kwargs[cls.__OptDict["same_branch"]] is True)): + if (cls.__OptDict["same_branch"] in kwargs) and ( + kwargs[cls.__OptDict["same_branch"]] is True + ): res = _exec("git describe --tags --first-parent --abbrev=0") else: res = _exec("git rev-list --tags --date-order --max-count=1") - if len(res)==1: + if len(res) == 1: res = _exec(f"git describe --tags {res[0]}") - if len(res)==0: + if len(res) == 0: raise cls.tagNotFound("no tag found in commit history") - if len(res)!=1: #pragma: nocover + if len(res) != 1: # pragma: nocover raise cls.moreThanOneTag("multiple tags on same commit is unsupported") return res[0] @classmethod - def getDistanceFromTag(cls,tag:str=None,**kwargs) -> int: + def getDistanceFromTag(cls, tag: str = None, **kwargs) -> int: """ retrieve the distance between HEAD and tag in the repository Arguments: @@ -289,23 +340,27 @@ class gitversionhelper: # pylint: disable=too-few-public-methods """ class containing methods focusing on versions """ - __OptDict = { "version_std": "version_std", - "formated_output": "formated_output", - "output_format": "output_format", - "ignore_unknown_tags": "ignore_unknown_tags"} + + __OptDict = { + "version_std": "version_std", + "formated_output": "formated_output", + "output_format": "output_format", + "ignore_unknown_tags": "ignore_unknown_tags", + } DefaultInputFormat = "Auto" - VersionStds = { "SemVer" : { "regex" : r"^(?P0|[1-9]\d*)\.(?P0|[1-9]\d*)\.(?P0|[1-9]\d*)"\ - r"(?:-(?P(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)"\ - r"(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?"\ - r"(?:\+(?P[0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$", - "regex_preversion_num": r"(?:\.)(?P(?:\d+(?!\w))+)", - "regex_build_num" : r"(?:\.)(?P(?:\d+(?!\w))+)" - }, - "PEP440" : { "regex" : packaging_VERSION_PATTERN, - "Auto" : None - } + VersionStds = { + "SemVer": { + "regex": r"^(?P0|[1-9]\d*)\.(?P0|[1-9]\d*)\.(?P0|[1-9]\d*)" + r"(?:-(?P(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)" + r"(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?" + r"(?:\+(?P[0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$", + "regex_preversion_num": r"(?:\.)(?P(?:\d+(?!\w))+)", + "regex_build_num": r"(?:\.)(?P(?:\d+(?!\w))+)", + }, + "PEP440": {"regex": packaging_VERSION_PATTERN, "Auto": None}, } __versionReseted = False + class versionException(gitversionhelperException): """ generic version exception @@ -326,33 +381,44 @@ class gitversionhelper: # pylint: disable=too-few-public-methods generic version object """ - __OptDict = { "bump_type": "bump_type", - "bump_dev_strategy": "bump_dev_strategy", - "formated_output": "formated_output"} - DefaultBumpType = "patch" - BumpTypes = ["major","minor","patch","dev"] - DefaultBumpDevStrategy = "post" - BumpDevStrategys = ["post","pre-patch","pre-minor","pre-major"] + __OptDict = { + "bump_type": "bump_type", + "bump_dev_strategy": "bump_dev_strategy", + "formated_output": "formated_output", + } + DefaultBumpType = "patch" + BumpTypes = ["major", "minor", "patch", "dev"] + DefaultBumpDevStrategy = "post" + BumpDevStrategys = ["post", "pre-patch", "pre-minor", "pre-major"] - version_std: str = "None" - major: int = 0 - minor: int = 1 - patch: int = 0 - pre_count:int = 0 - post_count:int = 0 - raw:str = "0.1.0" + version_std: str = "None" + major: int = 0 + minor: int = 1 + patch: int = 0 + pre_count: int = 0 + post_count: int = 0 + raw: str = "0.1.0" - def __init__(self,version_std,major=0,minor=1,patch=0,pre_count=0,post_count=0,raw="0.1.0"): #pylint: disable=R0913 + def __init__( + self, + version_std, + major=0, + minor=1, + patch=0, + pre_count=0, + post_count=0, + raw="0.1.0", + ): # pylint: disable=R0913 self.version_std = version_std - self.major = major - self.minor = minor - self.patch = patch - self.pre_count = pre_count - self.post_count = post_count - self.raw = raw + self.major = major + self.minor = minor + self.patch = patch + self.pre_count = pre_count + self.post_count = post_count + self.raw = raw @classmethod - def _getBumpDevStrategy(cls,**kwargs) -> str: + def _getBumpDevStrategy(cls, **kwargs) -> str: """ get selected bump_dev_strategy Keyword Arguments: @@ -362,14 +428,19 @@ class gitversionhelper: # pylint: disable=too-few-public-methods """ BumpDevStrategy = cls.DefaultBumpDevStrategy if cls.__OptDict["bump_dev_strategy"] in kwargs: - if kwargs[cls.__OptDict["bump_dev_strategy"]] in cls.BumpDevStrategys: + if ( + kwargs[cls.__OptDict["bump_dev_strategy"]] + in cls.BumpDevStrategys + ): BumpDevStrategy = kwargs[cls.__OptDict["bump_dev_strategy"]] else: - raise gitversionhelper.wrongArguments(f"invalid {cls.__OptDict['bump_type']} requested") + raise gitversionhelper.wrongArguments( + f"invalid {cls.__OptDict['bump_type']} requested" + ) return BumpDevStrategy @classmethod - def _getBumpType(cls,**kwargs) -> str: + def _getBumpType(cls, **kwargs) -> str: """ get selected bump_type Keyword Arguments: @@ -382,10 +453,14 @@ class gitversionhelper: # pylint: disable=too-few-public-methods if kwargs[cls.__OptDict["bump_type"]] in cls.BumpTypes: BumpType = kwargs[cls.__OptDict["bump_type"]] else: - raise gitversionhelper.wrongArguments(f"invalid {cls.__OptDict['bump_type']} requested") + raise gitversionhelper.wrongArguments( + f"invalid {cls.__OptDict['bump_type']} requested" + ) return BumpType - def bump(self,amount:int=1,**kwargs) -> gitversionhelper.version.MetaVersion | str : # pylint: disable=R0912 + def bump( + self, amount: int = 1, **kwargs + ) -> gitversionhelper.version.MetaVersion | str: # pylint: disable=R0912 """ bump the version to the next one Keyword Arguments: @@ -395,8 +470,8 @@ class gitversionhelper: # pylint: disable=too-few-public-methods the bumped version """ BumpType = self._getBumpType(**kwargs) - BumpDevStrategy=self._getBumpDevStrategy(**kwargs) - _v=copy(self) + BumpDevStrategy = self._getBumpDevStrategy(**kwargs) + _v = copy(self) if BumpType == "dev": if BumpDevStrategy == "post": @@ -404,7 +479,7 @@ class gitversionhelper: # pylint: disable=too-few-public-methods _v.pre_count = _v.pre_count + amount else: _v.post_count = _v.post_count + amount - #elif BumpDevStrategy in ["pre-patch","pre-minor","pre-major"]: + # elif BumpDevStrategy in ["pre-patch","pre-minor","pre-major"]: else: if _v.post_count > 0: _v.post_count = _v.post_count + amount @@ -415,7 +490,7 @@ class gitversionhelper: # pylint: disable=too-few-public-methods elif BumpDevStrategy == "pre-minor": _v.minor = _v.minor + 1 _v.patch = 0 - #elif BumpDevStrategy == "pre-major": + # elif BumpDevStrategy == "pre-major": else: _v.major = _v.major + 1 _v.minor = 0 @@ -426,18 +501,20 @@ class gitversionhelper: # pylint: disable=too-few-public-methods _v.major = _v.major + amount elif BumpType == "minor": _v.minor = _v.minor + amount - #elif BumpType == "patch": + # elif BumpType == "patch": else: _v.patch = _v.patch + amount - _v.pre_count=0 - _v.post_count=0 - _v.raw=_v.doFormatVersion(**kwargs) + _v.pre_count = 0 + _v.post_count = 0 + _v.raw = _v.doFormatVersion(**kwargs) - if ((self.__OptDict["formated_output"] in kwargs) and (kwargs[self.__OptDict["formated_output"]] is True)): + if (self.__OptDict["formated_output"] in kwargs) and ( + kwargs[self.__OptDict["formated_output"]] is True + ): return _v.doFormatVersion(**kwargs) return _v - def doFormatVersion(self,**kwargs) -> str: + def doFormatVersion(self, **kwargs) -> str: """ output a formated version string Keyword Arguments: @@ -445,10 +522,10 @@ class gitversionhelper: # pylint: disable=too-few-public-methods Returns: formated version string """ - return gitversionhelper.version.doFormatVersion(self,**kwargs) + return gitversionhelper.version.doFormatVersion(self, **kwargs) @classmethod - def _getVersionStd(cls,**kwargs) -> str: + def _getVersionStd(cls, **kwargs) -> str: """ get selected version_std Keyword Arguments: @@ -461,11 +538,13 @@ class gitversionhelper: # pylint: disable=too-few-public-methods if kwargs[cls.__OptDict["version_std"]] in cls.VersionStds: VersionStd = kwargs[cls.__OptDict["version_std"]] else: - raise gitversionhelper.wrongArguments(f"invalid {cls.__OptDict['version_std']} requested") + raise gitversionhelper.wrongArguments( + f"invalid {cls.__OptDict['version_std']} requested" + ) return VersionStd @classmethod - def getCurrentVersion(cls,**kwargs) -> MetaVersion | str : + def getCurrentVersion(cls, **kwargs) -> MetaVersion | str: """ get the current version or bump depending of repository state Keyword Arguments: @@ -478,8 +557,10 @@ class gitversionhelper: # pylint: disable=too-few-public-methods the last version """ if gitversionhelper.repository.isDirty() is not False: - raise gitversionhelper.repository.repositoryDirty( "The repository is dirty and a current version" \ - " can not be generated.") + raise gitversionhelper.repository.repositoryDirty( + "The repository is dirty and a current version" + " can not be generated." + ) saved_kwargs = copy(kwargs) if "formated_output" in kwargs: del saved_kwargs["formated_output"] @@ -487,23 +568,25 @@ class gitversionhelper: # pylint: disable=too-few-public-methods _v = cls.getLastVersion(**saved_kwargs) if not cls.__versionReseted: - amount = gitversionhelper.tag.getDistanceFromTag(_v.raw,**kwargs) - _v = _v.bump(amount,**saved_kwargs) + amount = gitversionhelper.tag.getDistanceFromTag(_v.raw, **kwargs) + _v = _v.bump(amount, **saved_kwargs) - if ((cls.__OptDict["formated_output"] in kwargs) and (kwargs[cls.__OptDict["formated_output"]] is True)): + if (cls.__OptDict["formated_output"] in kwargs) and ( + kwargs[cls.__OptDict["formated_output"]] is True + ): return _v.doFormatVersion(**kwargs) return _v @classmethod - def getCurrentFormatedVersion(cls,**kwargs) -> str : + def getCurrentFormatedVersion(cls, **kwargs) -> str: """ Same as getCurrentVersion() with formated_output kwarg activated """ - kwargs["formated_output"]=True + kwargs["formated_output"] = True return cls.getCurrentVersion(kwargs) @classmethod - def _parseTag(cls,tag,**kwargs): # pylint: disable=R0914, R0912, R0915 + def _parseTag(cls, tag, **kwargs): # pylint: disable=R0914, R0912, R0915 """get the last version from tags Arguments: tag: the tag to be parsed @@ -518,61 +601,84 @@ class gitversionhelper: # pylint: disable=too-few-public-methods if VersionStd == "Auto": bAutoVersionStd = True bFound = False - if VersionStd == "SemVer" or (bAutoVersionStd is True) : - _r=re.compile(r"^\s*" + cls.VersionStds["SemVer"]["regex"] + r"\s*$", re.VERBOSE | \ - re.IGNORECASE) - _m = re.match(_r,tag) + if VersionStd == "SemVer" or (bAutoVersionStd is True): + _r = re.compile( + r"^\s*" + cls.VersionStds["SemVer"]["regex"] + r"\s*$", + re.VERBOSE | re.IGNORECASE, + ) + _m = re.match(_r, tag) if not _m: pass else: - major, minor, patch = int(_m.group("major")),\ - int(_m.group("minor")),\ - int(_m.group("patch")) + major, minor, patch = ( + int(_m.group("major")), + int(_m.group("minor")), + int(_m.group("patch")), + ) pre_count = 0 if _pre := _m.group("prerelease"): - if (_match := re.search (cls.VersionStds["SemVer"]["regex_preversion_num"],_pre)) is not None: + if ( + _match := re.search( + cls.VersionStds["SemVer"]["regex_preversion_num"], _pre + ) + ) is not None: pre_count = int(_match.group("num")) else: pre_count = 1 post_count = 0 if _post := _m.group("buildmetadata"): - if (_match := re.search (cls.VersionStds["SemVer"]["regex_build_num"],_post)) is not None: + if ( + _match := re.search( + cls.VersionStds["SemVer"]["regex_build_num"], _post + ) + ) is not None: post_count = int(_match.group("num")) else: post_count = 1 bFound = True VersionStd = "SemVer" - if VersionStd == "PEP440" or ( (bAutoVersionStd is True) and (bFound is not True)): - _r=re.compile(r"^\s*" + cls.VersionStds["PEP440"]["regex"] + r"\s*$", re.VERBOSE | \ - re.IGNORECASE) - _m = re.match(_r,tag) + if VersionStd == "PEP440" or ( + (bAutoVersionStd is True) and (bFound is not True) + ): + _r = re.compile( + r"^\s*" + cls.VersionStds["PEP440"]["regex"] + r"\s*$", + re.VERBOSE | re.IGNORECASE, + ) + _m = re.match(_r, tag) if not _m: pass else: - ver=_m.group("release").split(".") + ver = _m.group("release").split(".") ver += ["0"] * (3 - len(ver)) - ver[0]=int(ver[0]) - ver[1]=int(ver[1]) - ver[2]=int(ver[2]) + ver[0] = int(ver[0]) + ver[1] = int(ver[1]) + ver[2] = int(ver[2]) major, minor, patch = tuple(ver) - pre_count = int(_m.group("pre_n")) if _m.group("pre_n") else 0 + pre_count = int(_m.group("pre_n")) if _m.group("pre_n") else 0 post_count = int(_m.group("post_n2")) if _m.group("post_n2") else 0 bFound = True VersionStd = "PEP440" - if not bFound : - raise gitversionhelper.version.noValidVersion("no valid version found in tags") + if not bFound: + raise gitversionhelper.version.noValidVersion( + "no valid version found in tags" + ) if pre_count > 0 and post_count > 0: - raise cls.PreAndPostVersionUnsupported("can not parse a version with both pre" \ - " and post release number.") - return cls.MetaVersion(VersionStd, major, minor, patch, pre_count, post_count, tag) + raise cls.PreAndPostVersionUnsupported( + "can not parse a version with both pre" " and post release number." + ) + return cls.MetaVersion( + VersionStd, major, minor, patch, pre_count, post_count, tag + ) @classmethod - def getLastVersion(cls,**kwargs) -> MetaVersion | str : # pylint: disable=R0914, R0912, R0915 + def getLastVersion( + cls, **kwargs + ) -> MetaVersion | str: # pylint: disable=R0914, R0912, R0915 """get the last version from tags Keyword Arguments: version_std(str): the given version_std (can be None) @@ -582,36 +688,40 @@ class gitversionhelper: # pylint: disable=too-few-public-methods Returns: the last version """ - lastTag=cls.MetaVersion.raw + lastTag = cls.MetaVersion.raw cls.__versionReseted = False try: lastTag = gitversionhelper.tag.getLastTag(**kwargs) except gitversionhelper.tag.tagNotFound: - logging.warning('tag not found, reseting versionning') + logging.warning("tag not found, reseting versionning") cls.__versionReseted = True - _v=None + _v = None try: - _v=cls._parseTag(lastTag,**kwargs) + _v = cls._parseTag(lastTag, **kwargs) except gitversionhelper.version.noValidVersion as _ex: - if ((cls.__OptDict["ignore_unknown_tags"] in kwargs) and (kwargs[cls.__OptDict["ignore_unknown_tags"]] is True)): - tags = gitversionhelper.tag.getTags(sort= "taggerdate",**kwargs) + if (cls.__OptDict["ignore_unknown_tags"] in kwargs) and ( + kwargs[cls.__OptDict["ignore_unknown_tags"]] is True + ): + tags = gitversionhelper.tag.getTags(sort="taggerdate", **kwargs) _v = None for _tag in tags: try: - _v=cls._parseTag(_tag,**kwargs) + _v = cls._parseTag(_tag, **kwargs) break except gitversionhelper.version.noValidVersion: continue if _v is None: raise gitversionhelper.version.noValidVersion() from _ex - if ((cls.__OptDict["formated_output"] in kwargs) and (kwargs[cls.__OptDict["formated_output"]] is True)): + if (cls.__OptDict["formated_output"] in kwargs) and ( + kwargs[cls.__OptDict["formated_output"]] is True + ): return _v.doFormatVersion(**kwargs) return _v @classmethod - def doFormatVersion(cls,inputversion:MetaVersion,**kwargs) -> str: + def doFormatVersion(cls, inputversion: MetaVersion, **kwargs) -> str: """ output a formated version string Keyword Arguments: @@ -623,41 +733,45 @@ class gitversionhelper: # pylint: disable=too-few-public-methods """ VersionStd = cls._getVersionStd(**kwargs) - if VersionStd=="Auto" : + if VersionStd == "Auto": VersionStd = inputversion.version_std OutputFormat = None - revpattern="" - revcount="" + revpattern = "" + revcount = "" post_count = inputversion.post_count - pre_count = inputversion.pre_count - patch = inputversion.patch + pre_count = inputversion.pre_count + patch = inputversion.patch if cls.__OptDict["output_format"] in kwargs: - OutputFormat=kwargs[cls.__OptDict["output_format"]] + OutputFormat = kwargs[cls.__OptDict["output_format"]] if OutputFormat is None: OutputFormat = "{major}.{minor}.{patch}{revpattern}{revcount}" if post_count > 0 and pre_count > 0: - raise gitversionhelper.version.PreAndPostVersionUnsupported("cannot output a version with both pre " \ - "and post release number.") - if VersionStd == "PEP440": + raise gitversionhelper.version.PreAndPostVersionUnsupported( + "cannot output a version with both pre " + "and post release number." + ) + if VersionStd == "PEP440": if post_count > 0: - revpattern=".post" - revcount=f"{post_count}" + revpattern = ".post" + revcount = f"{post_count}" elif pre_count > 0: - revpattern=".pre" - revcount=f"{pre_count}" - #elif VersionStd == "SemVer": + revpattern = ".pre" + revcount = f"{pre_count}" + # elif VersionStd == "SemVer": else: if post_count > 0: - revpattern="+post" - revcount=f".{post_count}" + revpattern = "+post" + revcount = f".{post_count}" elif pre_count > 0: - revpattern="-pre" - revcount=f".{pre_count}" - return OutputFormat.format( major=inputversion.major, \ - minor=inputversion.minor, \ - patch=patch, \ - revpattern=revpattern, \ - revcount=revcount) + revpattern = "-pre" + revcount = f".{pre_count}" + return OutputFormat.format( + major=inputversion.major, + minor=inputversion.minor, + patch=patch, + revpattern=revpattern, + revcount=revcount, + ) diff --git a/test/__init__.py b/test/__init__.py index bd46e02..5858865 100644 --- a/test/__init__.py +++ b/test/__init__.py @@ -4,4 +4,4 @@ # 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 . \ No newline at end of file +# work. If not, see .