From 248daa0fcb64232cb5ad8e638e0554e8704b929b Mon Sep 17 00:00:00 2001 From: Aleksander Kluczka <67486508+vis4rd@users.noreply.github.com> Date: Fri, 24 Nov 2023 01:05:18 +0100 Subject: [PATCH] Add support for CLI arguments (#2) * Add support for CLI arguments --- .vscode/extensions.json | 1 + .vscode/settings.json | 5 + README.md | 30 ++++- pydocstyle_gitlab_code_quality/main.py | 55 ++++++--- .../src/config/__init__.py | 0 .../src/config/cli_parser.py | 113 ++++++++++++++++++ .../src/config/config.py | 18 +++ .../src/utils/__init__.py | 0 .../src/{ => utils}/encoder.py | 0 .../src/utils/logger.py | 20 ++++ pyproject.toml | 5 +- 11 files changed, 223 insertions(+), 24 deletions(-) create mode 100644 pydocstyle_gitlab_code_quality/src/config/__init__.py create mode 100644 pydocstyle_gitlab_code_quality/src/config/cli_parser.py create mode 100644 pydocstyle_gitlab_code_quality/src/config/config.py create mode 100644 pydocstyle_gitlab_code_quality/src/utils/__init__.py rename pydocstyle_gitlab_code_quality/src/{ => utils}/encoder.py (100%) create mode 100644 pydocstyle_gitlab_code_quality/src/utils/logger.py diff --git a/.vscode/extensions.json b/.vscode/extensions.json index c0a246d..f64ad53 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -8,5 +8,6 @@ "ms-python.pylint", "ms-python.python", "ms-python.vscode-pylance", + "yzhang.markdown-all-in-one" ] } diff --git a/.vscode/settings.json b/.vscode/settings.json index 859b6b2..4756186 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -9,6 +9,11 @@ "editor.defaultFormatter": "ms-python.black-formatter", "editor.formatOnSave": true, }, + "[markdown]": { + "editor.defaultFormatter": "yzhang.markdown-all-in-one", + "editor.formatOnSave": true, + "editor.wordWrap": "off", + }, "files.eol": "\n", "files.trimTrailingWhitespace": true, "files.insertFinalNewline": true, diff --git a/README.md b/README.md index fb772e0..130a6c1 100644 --- a/README.md +++ b/README.md @@ -11,10 +11,34 @@ Generate [GitLab Code Quality report](https://docs.gitlab.com/ee/ci/testing/code ## Usage ```bash -$ pydocstyle | pydocstyle-gitlab-code-quality +# passing pydocstyle output through stdin (output printed to stdout) +pydocstyle main.py | pydocstyle-gitlab-code-quality > codequality.json +# or +pydocstyle-gitlab-code-quality < pydocstyle_out.txt > codequality.json + +# using CLI flags (output printed directly to a file) +pydocstyle-gitlab-code-quality --input pydocstyle_out.txt --output codequality.json ``` -The output of this command is printed to `stdout` in JSON format, which can be used as Code Quality report. +## CLI configuration + +`pydocstyle-gitlab-code-quality` allows for the following CLI arguments: + +| flag | example | default | description | +| ---------------------------- | ----------------------- | ----------------- | ------------------------------------------------------------- | +| `--minor ...` | `--minor=D100,D101` | *empty* | Error codes to be displayed with MINOR severity. | +| `--major ...` | `--major=D102,D103` | *empty* | Error codes to be displayed with MAJOR severity. | +| `--critical ...` | `--critical=D104,D105` | *empty* | Error codes to be displayed with CRITICAL severity. | +| `-i, --ignore ...` | `--ignore=D106,D107` | *empty* | Error codes to be omitted from Code Quality report. | +| `-f, --file, --input ` | `-f pydocstyle_out.txt` | *empty* | Path to the file with pydocstyle output. | +| `-o, --output ` | `-o codequality.json` | *empty* | Path to the file where the Code Quality report will be saved. | +| `--no-stdout` | N/A | `False` | Do not print the Code Quality report to stdout. | +| `--log-file ` | `--log-file latest.log` | `pgcq_latest.log` | Path to the file where the log will be saved. | +| `--enable-logging` | N/A | `False` | Enable logging to a file. For debugging purposes only. | + +By default, all error codes are reported with INFO severity. + +In case the same error code from `pydocstyle` has been provided to many severity options, the highest severity level takes precedence. ### Example `.gitlab-ci.yml` file @@ -37,7 +61,7 @@ codequality: script: - pip install pydocstyle pydocstyle-gitlab-code-quality - pydocstyle program.py > pydocstyle-out.txt - - pydocstyle-gitlab-code-quality < pydocstyle-out.txt > codequality.json + - pydocstyle-gitlab-code-quality --input pydocstyle-out.txt --output codequality.json artifacts: when: always reports: diff --git a/pydocstyle_gitlab_code_quality/main.py b/pydocstyle_gitlab_code_quality/main.py index 2c8470f..c50b0e9 100644 --- a/pydocstyle_gitlab_code_quality/main.py +++ b/pydocstyle_gitlab_code_quality/main.py @@ -1,11 +1,14 @@ import json +import logging as log import re from hashlib import md5 -from sys import stdin -from typing import Generator, TextIO +from typing import Dict, Generator, List, TextIO +from .src.config.cli_parser import CliParser +from .src.config.config import Config from .src.cq_types import Issue, LinesStructure, LocationStructure -from .src.encoder import DataclassJSONEncoder +from .src.utils.encoder import DataclassJSONEncoder +from .src.utils.logger import initialize_logging def get_pydocstyle_output(output: TextIO) -> Generator[dict, None, None]: @@ -22,6 +25,9 @@ def get_pydocstyle_output(output: TextIO) -> Generator[dict, None, None]: except StopIteration: return + if not brief_line or not details_line: + return + brief_line = brief_line.rstrip("\n") details_line = details_line.rstrip("\n") @@ -35,28 +41,39 @@ def get_pydocstyle_output(output: TextIO) -> Generator[dict, None, None]: errors = match_brief.groupdict() errors.update(match_details.groupdict()) + yield errors -def get_code_quality_issues() -> Generator: - output = get_pydocstyle_output(stdin) +def get_code_quality_issues() -> Generator[Issue, None, None]: + log.info(f"Input sink = {Config.input_sink}") + output = get_pydocstyle_output(Config.input_sink) + + severity_mapper: Dict[int, str] = {0: "info", 1: "minor", 2: "major", 3: "critical"} for entry in output: - yield Issue( - type="issue", - check_name=entry["error_code"], - description=entry["details"], - categories=["Style"], - severity="info", - location=LocationStructure( - path=entry["path"], - lines=LinesStructure(begin=int(entry["line"])), - ), - fingerprint=md5("".join(entry.values()).encode("utf-8")).hexdigest(), - ) + severity_index: int = Config.code_severities[entry["error_code"]] + if severity_index >= 0: + yield Issue( + type="issue", + check_name=entry["error_code"], + description=entry["details"], + categories=["Style"], + severity=severity_mapper[severity_index], + location=LocationStructure( + path=entry["path"], + lines=LinesStructure(begin=int(entry["line"])), + ), + fingerprint=md5("".join(entry.values()).encode("utf-8")).hexdigest(), + ) def main() -> None: - issues: list = list(get_code_quality_issues()) + CliParser.initialize() + initialize_logging() + + issues: List[Issue] = list(get_code_quality_issues()) json_output: str = json.dumps(issues, indent="\t", cls=DataclassJSONEncoder) - print(json_output) + + for sink in Config.output_sinks: + sink.write(json_output) diff --git a/pydocstyle_gitlab_code_quality/src/config/__init__.py b/pydocstyle_gitlab_code_quality/src/config/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/pydocstyle_gitlab_code_quality/src/config/cli_parser.py b/pydocstyle_gitlab_code_quality/src/config/cli_parser.py new file mode 100644 index 0000000..87a761a --- /dev/null +++ b/pydocstyle_gitlab_code_quality/src/config/cli_parser.py @@ -0,0 +1,113 @@ +import argparse +import logging as log + +from .config import Config + + +class CliParser: + parser: argparse.ArgumentParser = argparse.ArgumentParser( + description="Generate GitLab Code Quality report from an output of pydocstyle.", + add_help=True, + allow_abbrev=False, + ) + + @classmethod + def initialize(cls) -> None: + cls._setup_arguments() + cls._update_config() + + @classmethod + def _setup_arguments(cls) -> None: + cls.parser.add_argument( + "--minor", + help="Error codes to be displayed with MINOR severity.", + default=[], + type=lambda s: s.split(","), + ) + cls.parser.add_argument( + "--major", + help="Error codes to be displayed with MAJOR severity.", + default=[], + type=lambda s: s.split(","), + ) + cls.parser.add_argument( + "--critical", + help="Error codes to be displayed with CRITICAL severity.", + default=[], + type=lambda s: s.split(","), + ) + cls.parser.add_argument( + "-i", + "--ignore", + help="Error codes to be omitted from Code Quality report.", + default="", + type=lambda s: s.split(","), + ) + cls.parser.add_argument( + "-f", + "--file", + "--input", + help="Path to the file with pydocstyle output.", + default="", + type=str, + ) + cls.parser.add_argument( + "-o", + "--output", + help="Path to the file where the Code Quality report will be saved.", + default="", + type=str, + ) + cls.parser.add_argument( + "--no-stdout", + help="Do not print the Code Quality report to stdout.", + action="store_true", + default=False, + ) + cls.parser.add_argument( + "--log-file", + help="Path to the file where the log will be saved.", + default=Config.log_file, + type=str, + ) + cls.parser.add_argument( + "--enable-logging", + help="Enable logging to a file.", + action="store_true", + default=False, + ) + + @classmethod + def _update_config(cls) -> None: + args = vars(cls.parser.parse_args()) + + if args.get("no_stdout", False): + Config.output_sinks.pop(0) + + Config.enable_logging = args.get("enable_logging", False) + Config.log_file = args.get("log_file", Config.log_file) + + if filepath := args.get("output", ""): + file = open(filepath, "w", encoding="utf-8") # pylint: disable=consider-using-with + Config.output_sinks.append(file) + + if filepath := args.get("file", ""): + file = open(filepath, "r", encoding="utf-8") # pylint: disable=consider-using-with + Config.input_sink = file + + if codes := args.get("minor", []): + for code in codes: + Config.code_severities[code] = 1 + + if codes := args.get("major", []): + for code in codes: + Config.code_severities[code] = 2 + + if codes := args.get("critical", []): + log.info(codes) + for code in codes: + Config.code_severities[code] = 3 + + if codes := args.get("ignore", []): + for code in codes: + Config.code_severities[code] = -1 diff --git a/pydocstyle_gitlab_code_quality/src/config/config.py b/pydocstyle_gitlab_code_quality/src/config/config.py new file mode 100644 index 0000000..044026c --- /dev/null +++ b/pydocstyle_gitlab_code_quality/src/config/config.py @@ -0,0 +1,18 @@ +import logging +from sys import stdin, stdout +from typing import Dict, List, TextIO + + +class Config: + enable_logging: bool = False + log_file: str = "pgcq_latest.log" + log_level: int = logging.INFO + + code_severities: Dict[str, int] = {} + code_severities.update({f"D1{i:02d}": 0 for i in range(0, 8)}) + code_severities.update({f"D2{i:02d}": 0 for i in range(0, 16)}) + code_severities.update({f"D3{i:02d}": 0 for i in range(0, 3)}) + code_severities.update({f"D4{i:02d}": 0 for i in range(0, 20)}) + + input_sink: TextIO = stdin + output_sinks: List[TextIO] = [stdout] diff --git a/pydocstyle_gitlab_code_quality/src/utils/__init__.py b/pydocstyle_gitlab_code_quality/src/utils/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/pydocstyle_gitlab_code_quality/src/encoder.py b/pydocstyle_gitlab_code_quality/src/utils/encoder.py similarity index 100% rename from pydocstyle_gitlab_code_quality/src/encoder.py rename to pydocstyle_gitlab_code_quality/src/utils/encoder.py diff --git a/pydocstyle_gitlab_code_quality/src/utils/logger.py b/pydocstyle_gitlab_code_quality/src/utils/logger.py new file mode 100644 index 0000000..fb07aa1 --- /dev/null +++ b/pydocstyle_gitlab_code_quality/src/utils/logger.py @@ -0,0 +1,20 @@ +import logging + +from ..config.config import Config + +# from sys import stdout + + +def initialize_logging() -> None: + root = logging.getLogger() + root.setLevel(Config.log_level) + root.disabled = not Config.enable_logging + + # stdout_handler = logging.StreamHandler(stdout) + # stdout_handler.setLevel(Config.log_level) + + if Config.enable_logging: + file_handler = logging.FileHandler(Config.log_file, mode="w", encoding="utf-8") + file_handler.setLevel(Config.log_level) + + root.addHandler(file_handler) diff --git a/pyproject.toml b/pyproject.toml index 33719b8..2fe0437 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "pydocstyle-gitlab-code-quality" -version = "0.0.3" +version = "0.1.0" authors = [ { name = "Aleksander Kluczka", email = "aleksander.kluczka@gmail.com" }, ] @@ -54,6 +54,7 @@ disable = [ "C0116", # missing method docstring "R0902", # too many instance attributes "R0903", # too few public methods - "W0102" # dangerous default value of an argument + "W0102", # dangerous default value of an argument + "W1203" # use lazy % formatting in logging functions ] ignore = ["__init__.py"]