Skip to content
This repository was archived by the owner on Nov 24, 2023. It is now read-only.

Commit 248daa0

Browse files
authored
Add support for CLI arguments (#2)
* Add support for CLI arguments
1 parent 346e6f3 commit 248daa0

File tree

11 files changed

+223
-24
lines changed

11 files changed

+223
-24
lines changed

.vscode/extensions.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,6 @@
88
"ms-python.pylint",
99
"ms-python.python",
1010
"ms-python.vscode-pylance",
11+
"yzhang.markdown-all-in-one"
1112
]
1213
}

.vscode/settings.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,11 @@
99
"editor.defaultFormatter": "ms-python.black-formatter",
1010
"editor.formatOnSave": true,
1111
},
12+
"[markdown]": {
13+
"editor.defaultFormatter": "yzhang.markdown-all-in-one",
14+
"editor.formatOnSave": true,
15+
"editor.wordWrap": "off",
16+
},
1217
"files.eol": "\n",
1318
"files.trimTrailingWhitespace": true,
1419
"files.insertFinalNewline": true,

README.md

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,34 @@ Generate [GitLab Code Quality report](https://docs.gitlab.com/ee/ci/testing/code
1111
## Usage
1212

1313
```bash
14-
$ pydocstyle <file_path> | pydocstyle-gitlab-code-quality
14+
# passing pydocstyle output through stdin (output printed to stdout)
15+
pydocstyle main.py | pydocstyle-gitlab-code-quality > codequality.json
16+
# or
17+
pydocstyle-gitlab-code-quality < pydocstyle_out.txt > codequality.json
18+
19+
# using CLI flags (output printed directly to a file)
20+
pydocstyle-gitlab-code-quality --input pydocstyle_out.txt --output codequality.json
1521
```
1622

17-
The output of this command is printed to `stdout` in JSON format, which can be used as Code Quality report.
23+
## CLI configuration
24+
25+
`pydocstyle-gitlab-code-quality` allows for the following CLI arguments:
26+
27+
| flag | example | default | description |
28+
| ---------------------------- | ----------------------- | ----------------- | ------------------------------------------------------------- |
29+
| `--minor <CODE>...` | `--minor=D100,D101` | *empty* | Error codes to be displayed with MINOR severity. |
30+
| `--major <CODE>...` | `--major=D102,D103` | *empty* | Error codes to be displayed with MAJOR severity. |
31+
| `--critical <CODE>...` | `--critical=D104,D105` | *empty* | Error codes to be displayed with CRITICAL severity. |
32+
| `-i, --ignore <CODE>...` | `--ignore=D106,D107` | *empty* | Error codes to be omitted from Code Quality report. |
33+
| `-f, --file, --input <FILE>` | `-f pydocstyle_out.txt` | *empty* | Path to the file with pydocstyle output. |
34+
| `-o, --output <FILE>` | `-o codequality.json` | *empty* | Path to the file where the Code Quality report will be saved. |
35+
| `--no-stdout` | N/A | `False` | Do not print the Code Quality report to stdout. |
36+
| `--log-file <FILE>` | `--log-file latest.log` | `pgcq_latest.log` | Path to the file where the log will be saved. |
37+
| `--enable-logging` | N/A | `False` | Enable logging to a file. For debugging purposes only. |
38+
39+
By default, all error codes are reported with INFO severity.
40+
41+
In case the same error code from `pydocstyle` has been provided to many severity options, the highest severity level takes precedence.
1842

1943
### Example `.gitlab-ci.yml` file
2044

@@ -37,7 +61,7 @@ codequality:
3761
script:
3862
- pip install pydocstyle pydocstyle-gitlab-code-quality
3963
- pydocstyle program.py > pydocstyle-out.txt
40-
- pydocstyle-gitlab-code-quality < pydocstyle-out.txt > codequality.json
64+
- pydocstyle-gitlab-code-quality --input pydocstyle-out.txt --output codequality.json
4165
artifacts:
4266
when: always
4367
reports:
Lines changed: 36 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
import json
2+
import logging as log
23
import re
34
from hashlib import md5
4-
from sys import stdin
5-
from typing import Generator, TextIO
5+
from typing import Dict, Generator, List, TextIO
66

7+
from .src.config.cli_parser import CliParser
8+
from .src.config.config import Config
79
from .src.cq_types import Issue, LinesStructure, LocationStructure
8-
from .src.encoder import DataclassJSONEncoder
10+
from .src.utils.encoder import DataclassJSONEncoder
11+
from .src.utils.logger import initialize_logging
912

1013

1114
def get_pydocstyle_output(output: TextIO) -> Generator[dict, None, None]:
@@ -22,6 +25,9 @@ def get_pydocstyle_output(output: TextIO) -> Generator[dict, None, None]:
2225
except StopIteration:
2326
return
2427

28+
if not brief_line or not details_line:
29+
return
30+
2531
brief_line = brief_line.rstrip("\n")
2632
details_line = details_line.rstrip("\n")
2733

@@ -35,28 +41,39 @@ def get_pydocstyle_output(output: TextIO) -> Generator[dict, None, None]:
3541

3642
errors = match_brief.groupdict()
3743
errors.update(match_details.groupdict())
44+
3845
yield errors
3946

4047

41-
def get_code_quality_issues() -> Generator:
42-
output = get_pydocstyle_output(stdin)
48+
def get_code_quality_issues() -> Generator[Issue, None, None]:
49+
log.info(f"Input sink = {Config.input_sink}")
50+
output = get_pydocstyle_output(Config.input_sink)
51+
52+
severity_mapper: Dict[int, str] = {0: "info", 1: "minor", 2: "major", 3: "critical"}
4353

4454
for entry in output:
45-
yield Issue(
46-
type="issue",
47-
check_name=entry["error_code"],
48-
description=entry["details"],
49-
categories=["Style"],
50-
severity="info",
51-
location=LocationStructure(
52-
path=entry["path"],
53-
lines=LinesStructure(begin=int(entry["line"])),
54-
),
55-
fingerprint=md5("".join(entry.values()).encode("utf-8")).hexdigest(),
56-
)
55+
severity_index: int = Config.code_severities[entry["error_code"]]
56+
if severity_index >= 0:
57+
yield Issue(
58+
type="issue",
59+
check_name=entry["error_code"],
60+
description=entry["details"],
61+
categories=["Style"],
62+
severity=severity_mapper[severity_index],
63+
location=LocationStructure(
64+
path=entry["path"],
65+
lines=LinesStructure(begin=int(entry["line"])),
66+
),
67+
fingerprint=md5("".join(entry.values()).encode("utf-8")).hexdigest(),
68+
)
5769

5870

5971
def main() -> None:
60-
issues: list = list(get_code_quality_issues())
72+
CliParser.initialize()
73+
initialize_logging()
74+
75+
issues: List[Issue] = list(get_code_quality_issues())
6176
json_output: str = json.dumps(issues, indent="\t", cls=DataclassJSONEncoder)
62-
print(json_output)
77+
78+
for sink in Config.output_sinks:
79+
sink.write(json_output)

pydocstyle_gitlab_code_quality/src/config/__init__.py

Whitespace-only changes.
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
import argparse
2+
import logging as log
3+
4+
from .config import Config
5+
6+
7+
class CliParser:
8+
parser: argparse.ArgumentParser = argparse.ArgumentParser(
9+
description="Generate GitLab Code Quality report from an output of pydocstyle.",
10+
add_help=True,
11+
allow_abbrev=False,
12+
)
13+
14+
@classmethod
15+
def initialize(cls) -> None:
16+
cls._setup_arguments()
17+
cls._update_config()
18+
19+
@classmethod
20+
def _setup_arguments(cls) -> None:
21+
cls.parser.add_argument(
22+
"--minor",
23+
help="Error codes to be displayed with MINOR severity.",
24+
default=[],
25+
type=lambda s: s.split(","),
26+
)
27+
cls.parser.add_argument(
28+
"--major",
29+
help="Error codes to be displayed with MAJOR severity.",
30+
default=[],
31+
type=lambda s: s.split(","),
32+
)
33+
cls.parser.add_argument(
34+
"--critical",
35+
help="Error codes to be displayed with CRITICAL severity.",
36+
default=[],
37+
type=lambda s: s.split(","),
38+
)
39+
cls.parser.add_argument(
40+
"-i",
41+
"--ignore",
42+
help="Error codes to be omitted from Code Quality report.",
43+
default="",
44+
type=lambda s: s.split(","),
45+
)
46+
cls.parser.add_argument(
47+
"-f",
48+
"--file",
49+
"--input",
50+
help="Path to the file with pydocstyle output.",
51+
default="",
52+
type=str,
53+
)
54+
cls.parser.add_argument(
55+
"-o",
56+
"--output",
57+
help="Path to the file where the Code Quality report will be saved.",
58+
default="",
59+
type=str,
60+
)
61+
cls.parser.add_argument(
62+
"--no-stdout",
63+
help="Do not print the Code Quality report to stdout.",
64+
action="store_true",
65+
default=False,
66+
)
67+
cls.parser.add_argument(
68+
"--log-file",
69+
help="Path to the file where the log will be saved.",
70+
default=Config.log_file,
71+
type=str,
72+
)
73+
cls.parser.add_argument(
74+
"--enable-logging",
75+
help="Enable logging to a file.",
76+
action="store_true",
77+
default=False,
78+
)
79+
80+
@classmethod
81+
def _update_config(cls) -> None:
82+
args = vars(cls.parser.parse_args())
83+
84+
if args.get("no_stdout", False):
85+
Config.output_sinks.pop(0)
86+
87+
Config.enable_logging = args.get("enable_logging", False)
88+
Config.log_file = args.get("log_file", Config.log_file)
89+
90+
if filepath := args.get("output", ""):
91+
file = open(filepath, "w", encoding="utf-8") # pylint: disable=consider-using-with
92+
Config.output_sinks.append(file)
93+
94+
if filepath := args.get("file", ""):
95+
file = open(filepath, "r", encoding="utf-8") # pylint: disable=consider-using-with
96+
Config.input_sink = file
97+
98+
if codes := args.get("minor", []):
99+
for code in codes:
100+
Config.code_severities[code] = 1
101+
102+
if codes := args.get("major", []):
103+
for code in codes:
104+
Config.code_severities[code] = 2
105+
106+
if codes := args.get("critical", []):
107+
log.info(codes)
108+
for code in codes:
109+
Config.code_severities[code] = 3
110+
111+
if codes := args.get("ignore", []):
112+
for code in codes:
113+
Config.code_severities[code] = -1
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import logging
2+
from sys import stdin, stdout
3+
from typing import Dict, List, TextIO
4+
5+
6+
class Config:
7+
enable_logging: bool = False
8+
log_file: str = "pgcq_latest.log"
9+
log_level: int = logging.INFO
10+
11+
code_severities: Dict[str, int] = {}
12+
code_severities.update({f"D1{i:02d}": 0 for i in range(0, 8)})
13+
code_severities.update({f"D2{i:02d}": 0 for i in range(0, 16)})
14+
code_severities.update({f"D3{i:02d}": 0 for i in range(0, 3)})
15+
code_severities.update({f"D4{i:02d}": 0 for i in range(0, 20)})
16+
17+
input_sink: TextIO = stdin
18+
output_sinks: List[TextIO] = [stdout]

pydocstyle_gitlab_code_quality/src/utils/__init__.py

Whitespace-only changes.
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import logging
2+
3+
from ..config.config import Config
4+
5+
# from sys import stdout
6+
7+
8+
def initialize_logging() -> None:
9+
root = logging.getLogger()
10+
root.setLevel(Config.log_level)
11+
root.disabled = not Config.enable_logging
12+
13+
# stdout_handler = logging.StreamHandler(stdout)
14+
# stdout_handler.setLevel(Config.log_level)
15+
16+
if Config.enable_logging:
17+
file_handler = logging.FileHandler(Config.log_file, mode="w", encoding="utf-8")
18+
file_handler.setLevel(Config.log_level)
19+
20+
root.addHandler(file_handler)

0 commit comments

Comments
 (0)