py-code-quality-guard 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- py_code_quality_guard-0.1.0/.github/workflows/ci.yml +20 -0
- py_code_quality_guard-0.1.0/.github/workflows/publish.yml +34 -0
- py_code_quality_guard-0.1.0/.gitignore +9 -0
- py_code_quality_guard-0.1.0/.pre-commit-config.yaml +21 -0
- py_code_quality_guard-0.1.0/.pre-commit-hooks.yaml +19 -0
- py_code_quality_guard-0.1.0/.python-version +1 -0
- py_code_quality_guard-0.1.0/PKG-INFO +145 -0
- py_code_quality_guard-0.1.0/README.md +133 -0
- py_code_quality_guard-0.1.0/pyproject.toml +41 -0
- py_code_quality_guard-0.1.0/src/code_quality_guard/__init__.py +3 -0
- py_code_quality_guard-0.1.0/src/code_quality_guard/__main__.py +4 -0
- py_code_quality_guard-0.1.0/src/code_quality_guard/application/__init__.py +1 -0
- py_code_quality_guard-0.1.0/src/code_quality_guard/application/service.py +9 -0
- py_code_quality_guard-0.1.0/src/code_quality_guard/cli.py +58 -0
- py_code_quality_guard-0.1.0/src/code_quality_guard/config/__init__.py +3 -0
- py_code_quality_guard-0.1.0/src/code_quality_guard/config/settings.py +15 -0
- py_code_quality_guard-0.1.0/src/code_quality_guard/reporter/__init__.py +1 -0
- py_code_quality_guard-0.1.0/src/code_quality_guard/reporter/console.py +20 -0
- py_code_quality_guard-0.1.0/src/code_quality_guard/reporter/json.py +15 -0
- py_code_quality_guard-0.1.0/src/code_quality_guard/runner.py +40 -0
- py_code_quality_guard-0.1.0/src/code_quality_guard/tools/__init__.py +1 -0
- py_code_quality_guard-0.1.0/src/code_quality_guard/tools/base.py +13 -0
- py_code_quality_guard-0.1.0/src/code_quality_guard/tools/command.py +12 -0
- py_code_quality_guard-0.1.0/src/code_quality_guard/tools/mypy.py +18 -0
- py_code_quality_guard-0.1.0/src/code_quality_guard/tools/registry.py +20 -0
- py_code_quality_guard-0.1.0/src/code_quality_guard/tools/ruff.py +20 -0
- py_code_quality_guard-0.1.0/src/code_quality_guard/tools/ruff_format.py +20 -0
- py_code_quality_guard-0.1.0/tests/__init__.py +0 -0
- py_code_quality_guard-0.1.0/tests/conftest.py +8 -0
- py_code_quality_guard-0.1.0/tests/test_checkers/__init__.py +0 -0
- py_code_quality_guard-0.1.0/tests/test_checkers/test_runner.py +10 -0
- py_code_quality_guard-0.1.0/tests/test_cli.py +33 -0
- py_code_quality_guard-0.1.0/tests/test_reporter.py +20 -0
- py_code_quality_guard-0.1.0/tests/test_tools.py +56 -0
- py_code_quality_guard-0.1.0/uv.lock +784 -0
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
pull_request:
|
|
5
|
+
branches: [main]
|
|
6
|
+
|
|
7
|
+
jobs:
|
|
8
|
+
quality:
|
|
9
|
+
runs-on: ubuntu-latest
|
|
10
|
+
steps:
|
|
11
|
+
- uses: actions/checkout@v4
|
|
12
|
+
- uses: astral-sh/setup-uv@v6
|
|
13
|
+
with:
|
|
14
|
+
enable-cache: true
|
|
15
|
+
- name: Set up Python
|
|
16
|
+
run: uv python install 3.12
|
|
17
|
+
- name: Install dependencies
|
|
18
|
+
run: uv sync --dev
|
|
19
|
+
- name: Run Linting, Formatting Checks, and Type Checking, Testing
|
|
20
|
+
run: uv run pre-commit run --all-files
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
release:
|
|
5
|
+
types: [published]
|
|
6
|
+
|
|
7
|
+
jobs:
|
|
8
|
+
pypi-publish:
|
|
9
|
+
name: Upload release to PyPI
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
permissions:
|
|
12
|
+
contents: read
|
|
13
|
+
env:
|
|
14
|
+
PYPI_API_TOKEN: ${{ secrets.PYPI_API_TOKEN }}
|
|
15
|
+
steps:
|
|
16
|
+
- name: Checkout code
|
|
17
|
+
uses: actions/checkout@v4
|
|
18
|
+
|
|
19
|
+
- name: Set up Python
|
|
20
|
+
uses: actions/setup-python@v5
|
|
21
|
+
with:
|
|
22
|
+
python-version: "3.x"
|
|
23
|
+
|
|
24
|
+
- name: Install build tools
|
|
25
|
+
run: python -m pip install --upgrade build
|
|
26
|
+
|
|
27
|
+
- name: Build package distributions
|
|
28
|
+
run: python -m build
|
|
29
|
+
|
|
30
|
+
- name: Publish package to PyPI
|
|
31
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
32
|
+
with:
|
|
33
|
+
user: __token__
|
|
34
|
+
password: ${{ env.PYPI_API_TOKEN }}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
repos:
|
|
2
|
+
- repo: local
|
|
3
|
+
hooks:
|
|
4
|
+
- id: ruff-check
|
|
5
|
+
name: Ruff lint
|
|
6
|
+
entry: uv run ruff check
|
|
7
|
+
language: system
|
|
8
|
+
pass_filenames: false
|
|
9
|
+
always_run: true
|
|
10
|
+
- id: ruff-format
|
|
11
|
+
name: Ruff format
|
|
12
|
+
entry: uv run ruff format --check
|
|
13
|
+
language: system
|
|
14
|
+
pass_filenames: false
|
|
15
|
+
always_run: true
|
|
16
|
+
- id: pytest
|
|
17
|
+
name: Pytest
|
|
18
|
+
entry: uv run pytest
|
|
19
|
+
language: system
|
|
20
|
+
pass_filenames: false
|
|
21
|
+
always_run: true
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
- id: cqg
|
|
2
|
+
name: Code Quality Guard
|
|
3
|
+
entry: code-quality-guard
|
|
4
|
+
language: python
|
|
5
|
+
language_version: python3.12
|
|
6
|
+
pass_filenames: false
|
|
7
|
+
require_serial: true
|
|
8
|
+
types: [python]
|
|
9
|
+
description: |
|
|
10
|
+
Run Ruff linting, Ruff formatting checks, and Mypy over the project.
|
|
11
|
+
args:
|
|
12
|
+
- --tool
|
|
13
|
+
- ruff
|
|
14
|
+
- --tool
|
|
15
|
+
- ruff-format
|
|
16
|
+
- --tool
|
|
17
|
+
- mypy
|
|
18
|
+
- --check-only
|
|
19
|
+
- .
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.12
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: py-code-quality-guard
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Automated Code Quality Guard for Python
|
|
5
|
+
Requires-Python: >=3.12
|
|
6
|
+
Requires-Dist: click>=8.1
|
|
7
|
+
Requires-Dist: mypy>=1.11
|
|
8
|
+
Requires-Dist: pydantic>=2.8
|
|
9
|
+
Requires-Dist: rich>=13.7
|
|
10
|
+
Requires-Dist: ruff>=0.6
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
|
|
13
|
+
# Code Quality Guard
|
|
14
|
+
|
|
15
|
+
[](https://github.com/karma369-labs/code-quality-guard/actions/workflows/ci.yml)
|
|
16
|
+
[](https://pypi.org/project/py-code-quality-guard/)
|
|
17
|
+
[](https://www.python.org/)
|
|
18
|
+
[](https://docs.astral.sh/ruff/)
|
|
19
|
+
[](https://mypy-lang.org/)
|
|
20
|
+
|
|
21
|
+
`py-code-quality-guard` is a CI-friendly command-line quality gate for Python
|
|
22
|
+
projects. It runs Ruff linting, Ruff formatting, and Mypy through one
|
|
23
|
+
consistent interface, with human-readable or JSON output for local workflows
|
|
24
|
+
and automation.
|
|
25
|
+
|
|
26
|
+
## Features
|
|
27
|
+
|
|
28
|
+
- Run Ruff, Ruff format, and Mypy independently or together.
|
|
29
|
+
- Check a project directory or a single file.
|
|
30
|
+
- Apply Ruff fixes when supported.
|
|
31
|
+
- Forward additional arguments to each selected tool.
|
|
32
|
+
- Emit text for developers or JSON for scripts and CI integrations.
|
|
33
|
+
- Return a non-zero exit code when any selected check fails.
|
|
34
|
+
|
|
35
|
+
## Requirements
|
|
36
|
+
|
|
37
|
+
- Python 3.12 or newer
|
|
38
|
+
- Ruff and Mypy are installed automatically with the package
|
|
39
|
+
|
|
40
|
+
## Installation
|
|
41
|
+
|
|
42
|
+
Install the latest release from PyPI in an active virtual environment:
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
python -m pip install py-code-quality-guard
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
For local development with `uv`:
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
uv sync --dev
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Pre-commit Hook
|
|
55
|
+
|
|
56
|
+
Add `py-code-quality-guard` to a project-level `.pre-commit-config.yaml`:
|
|
57
|
+
|
|
58
|
+
```yaml
|
|
59
|
+
repos:
|
|
60
|
+
- repo: https://github.com/karma369-labs/code-quality-guard
|
|
61
|
+
rev: v0.1.0
|
|
62
|
+
hooks:
|
|
63
|
+
- id: cqg
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Install the hook and run it against the repository:
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
python -m pip install pre-commit
|
|
70
|
+
pre-commit install
|
|
71
|
+
pre-commit run --all-files
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
The `cqg` hook runs Ruff linting, Ruff formatting checks, and Mypy over the
|
|
75
|
+
project. To use a newer release, update `rev` to the corresponding package
|
|
76
|
+
release tag.
|
|
77
|
+
|
|
78
|
+
## Usage
|
|
79
|
+
|
|
80
|
+
Run all checks against the current directory:
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
pyqguard .
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Run selected tools:
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
pyqguard --tool ruff --tool mypy src/
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Checks report the tools' diagnostics without changing files by default:
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
pyqguard --tool ruff-format .
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Apply supported fixes:
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
pyqguard --tool ruff --tool ruff-format --fix .
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Produce machine-readable output:
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
pyqguard --output json .
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
Pass an additional argument to a selected tool. Repeat `--tool-arg` as
|
|
111
|
+
needed:
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
pyqguard \
|
|
115
|
+
--tool ruff \
|
|
116
|
+
--tool-arg ruff --select \
|
|
117
|
+
--tool-arg ruff E,F \
|
|
118
|
+
.
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
Run `pyqguard --help` for the complete option reference.
|
|
122
|
+
|
|
123
|
+
## Development
|
|
124
|
+
|
|
125
|
+
Install the development dependencies and run the test suite:
|
|
126
|
+
|
|
127
|
+
```bash
|
|
128
|
+
uv sync --dev
|
|
129
|
+
uv run pytest
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
The repository uses pre-commit for linting, formatting, type checking, and
|
|
133
|
+
other repository checks:
|
|
134
|
+
|
|
135
|
+
```bash
|
|
136
|
+
uv run pre-commit install
|
|
137
|
+
uv run pre-commit run --all-files
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
Pull requests targeting `main` run the same pre-commit checks in GitHub Actions.
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
## License
|
|
144
|
+
|
|
145
|
+
License information has not yet been published for this repository.
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
# Code Quality Guard
|
|
2
|
+
|
|
3
|
+
[](https://github.com/karma369-labs/code-quality-guard/actions/workflows/ci.yml)
|
|
4
|
+
[](https://pypi.org/project/py-code-quality-guard/)
|
|
5
|
+
[](https://www.python.org/)
|
|
6
|
+
[](https://docs.astral.sh/ruff/)
|
|
7
|
+
[](https://mypy-lang.org/)
|
|
8
|
+
|
|
9
|
+
`py-code-quality-guard` is a CI-friendly command-line quality gate for Python
|
|
10
|
+
projects. It runs Ruff linting, Ruff formatting, and Mypy through one
|
|
11
|
+
consistent interface, with human-readable or JSON output for local workflows
|
|
12
|
+
and automation.
|
|
13
|
+
|
|
14
|
+
## Features
|
|
15
|
+
|
|
16
|
+
- Run Ruff, Ruff format, and Mypy independently or together.
|
|
17
|
+
- Check a project directory or a single file.
|
|
18
|
+
- Apply Ruff fixes when supported.
|
|
19
|
+
- Forward additional arguments to each selected tool.
|
|
20
|
+
- Emit text for developers or JSON for scripts and CI integrations.
|
|
21
|
+
- Return a non-zero exit code when any selected check fails.
|
|
22
|
+
|
|
23
|
+
## Requirements
|
|
24
|
+
|
|
25
|
+
- Python 3.12 or newer
|
|
26
|
+
- Ruff and Mypy are installed automatically with the package
|
|
27
|
+
|
|
28
|
+
## Installation
|
|
29
|
+
|
|
30
|
+
Install the latest release from PyPI in an active virtual environment:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
python -m pip install py-code-quality-guard
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
For local development with `uv`:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
uv sync --dev
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Pre-commit Hook
|
|
43
|
+
|
|
44
|
+
Add `py-code-quality-guard` to a project-level `.pre-commit-config.yaml`:
|
|
45
|
+
|
|
46
|
+
```yaml
|
|
47
|
+
repos:
|
|
48
|
+
- repo: https://github.com/karma369-labs/code-quality-guard
|
|
49
|
+
rev: v0.1.0
|
|
50
|
+
hooks:
|
|
51
|
+
- id: cqg
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Install the hook and run it against the repository:
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
python -m pip install pre-commit
|
|
58
|
+
pre-commit install
|
|
59
|
+
pre-commit run --all-files
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
The `cqg` hook runs Ruff linting, Ruff formatting checks, and Mypy over the
|
|
63
|
+
project. To use a newer release, update `rev` to the corresponding package
|
|
64
|
+
release tag.
|
|
65
|
+
|
|
66
|
+
## Usage
|
|
67
|
+
|
|
68
|
+
Run all checks against the current directory:
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
pyqguard .
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Run selected tools:
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
pyqguard --tool ruff --tool mypy src/
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Checks report the tools' diagnostics without changing files by default:
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
pyqguard --tool ruff-format .
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Apply supported fixes:
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
pyqguard --tool ruff --tool ruff-format --fix .
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Produce machine-readable output:
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
pyqguard --output json .
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Pass an additional argument to a selected tool. Repeat `--tool-arg` as
|
|
99
|
+
needed:
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
pyqguard \
|
|
103
|
+
--tool ruff \
|
|
104
|
+
--tool-arg ruff --select \
|
|
105
|
+
--tool-arg ruff E,F \
|
|
106
|
+
.
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
Run `pyqguard --help` for the complete option reference.
|
|
110
|
+
|
|
111
|
+
## Development
|
|
112
|
+
|
|
113
|
+
Install the development dependencies and run the test suite:
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
uv sync --dev
|
|
117
|
+
uv run pytest
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
The repository uses pre-commit for linting, formatting, type checking, and
|
|
121
|
+
other repository checks:
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
uv run pre-commit install
|
|
125
|
+
uv run pre-commit run --all-files
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Pull requests targeting `main` run the same pre-commit checks in GitHub Actions.
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
## License
|
|
132
|
+
|
|
133
|
+
License information has not yet been published for this repository.
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "py-code-quality-guard"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Automated Code Quality Guard for Python"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.12"
|
|
11
|
+
dependencies = [
|
|
12
|
+
"click>=8.1",
|
|
13
|
+
"mypy>=1.11",
|
|
14
|
+
"pydantic>=2.8",
|
|
15
|
+
"rich>=13.7",
|
|
16
|
+
"ruff>=0.6",
|
|
17
|
+
]
|
|
18
|
+
|
|
19
|
+
[project.scripts]
|
|
20
|
+
pyqguard = "code_quality_guard.cli:main"
|
|
21
|
+
|
|
22
|
+
[dependency-groups]
|
|
23
|
+
dev = [
|
|
24
|
+
"pre-commit>=3.8",
|
|
25
|
+
"pytest>=8.3",
|
|
26
|
+
"pytest-cov>=5.0",
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
[tool.hatch.build.targets.wheel]
|
|
30
|
+
packages = ["src/code_quality_guard"]
|
|
31
|
+
|
|
32
|
+
[tool.pytest.ini_options]
|
|
33
|
+
testpaths = ["tests"]
|
|
34
|
+
pythonpath = ["src"]
|
|
35
|
+
|
|
36
|
+
[tool.ruff]
|
|
37
|
+
line-length = 100
|
|
38
|
+
target-version = "py312"
|
|
39
|
+
|
|
40
|
+
[tool.ruff.lint]
|
|
41
|
+
select = ["E", "F", "I"]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Application orchestration for quality checks."""
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from code_quality_guard.config.settings import Settings
|
|
4
|
+
from code_quality_guard.runner import CommandResult
|
|
5
|
+
from code_quality_guard.tools.registry import build_tools
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def run_quality_checks(settings: Settings) -> dict[str, CommandResult]:
|
|
9
|
+
return {tool.name: tool.run(settings) for tool in build_tools(settings.tools)}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
import click
|
|
6
|
+
|
|
7
|
+
from code_quality_guard.application.service import run_quality_checks
|
|
8
|
+
from code_quality_guard.config.settings import Settings
|
|
9
|
+
from code_quality_guard.reporter.console import render_console
|
|
10
|
+
from code_quality_guard.reporter.json import render_json
|
|
11
|
+
|
|
12
|
+
TOOL_NAMES = ["ruff", "ruff-format", "mypy"]
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@click.command()
|
|
16
|
+
@click.argument("target", type=click.Path(path_type=Path, exists=True), default=Path.cwd())
|
|
17
|
+
@click.option("--tool", "tools", multiple=True, type=click.Choice(TOOL_NAMES))
|
|
18
|
+
@click.option("--fix", is_flag=True, help="Apply supported fixes.")
|
|
19
|
+
@click.option("--check-only", is_flag=True, help="Check formatting without changing files.")
|
|
20
|
+
@click.option("--output", type=click.Choice(["text", "json"]), default="text", show_default=True)
|
|
21
|
+
@click.option(
|
|
22
|
+
"--tool-arg",
|
|
23
|
+
"tool_args",
|
|
24
|
+
nargs=2,
|
|
25
|
+
multiple=True,
|
|
26
|
+
metavar="TOOL ARG",
|
|
27
|
+
help="Pass an additional argument to a tool; repeat as needed.",
|
|
28
|
+
)
|
|
29
|
+
def main(
|
|
30
|
+
target: Path,
|
|
31
|
+
tools: tuple[str, ...],
|
|
32
|
+
fix: bool,
|
|
33
|
+
check_only: bool,
|
|
34
|
+
output: str,
|
|
35
|
+
tool_args: tuple[tuple[str, str], ...],
|
|
36
|
+
) -> None:
|
|
37
|
+
"""Run selected code quality tools against TARGET."""
|
|
38
|
+
selected_tools = tools or tuple(TOOL_NAMES)
|
|
39
|
+
args_by_tool: dict[str, tuple[str, ...]] = {}
|
|
40
|
+
for tool_name, argument in tool_args:
|
|
41
|
+
if tool_name not in selected_tools:
|
|
42
|
+
raise click.BadParameter(f"{tool_name!r} is not selected with --tool")
|
|
43
|
+
args_by_tool[tool_name] = (*args_by_tool.get(tool_name, ()), argument)
|
|
44
|
+
settings = Settings(
|
|
45
|
+
target=target.resolve(),
|
|
46
|
+
tools=selected_tools,
|
|
47
|
+
fix=fix,
|
|
48
|
+
check_only=check_only,
|
|
49
|
+
output=output,
|
|
50
|
+
tool_args=args_by_tool,
|
|
51
|
+
)
|
|
52
|
+
results = run_quality_checks(settings)
|
|
53
|
+
if settings.output == "json":
|
|
54
|
+
click.echo(render_json(results))
|
|
55
|
+
else:
|
|
56
|
+
render_console(results)
|
|
57
|
+
if not all(result.passed for result in results.values()):
|
|
58
|
+
raise click.exceptions.Exit(1)
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
from typing import Literal
|
|
3
|
+
|
|
4
|
+
from pydantic import BaseModel, ConfigDict, Field
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Settings(BaseModel):
|
|
8
|
+
model_config = ConfigDict(arbitrary_types_allowed=True)
|
|
9
|
+
|
|
10
|
+
target: Path = Field(default_factory=Path.cwd)
|
|
11
|
+
tools: tuple[str, ...] = ("ruff", "mypy")
|
|
12
|
+
fix: bool = False
|
|
13
|
+
check_only: bool = False
|
|
14
|
+
output: Literal["text", "json"] = "text"
|
|
15
|
+
tool_args: dict[str, tuple[str, ...]] = Field(default_factory=dict)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Human and machine-readable reporting."""
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from rich.console import Console
|
|
4
|
+
|
|
5
|
+
from code_quality_guard.runner import CommandResult
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def render_console(results: dict[str, CommandResult]) -> None:
|
|
9
|
+
console = Console()
|
|
10
|
+
for name, result in results.items():
|
|
11
|
+
status = "PASS" if result.passed else "FAIL"
|
|
12
|
+
color = "green" if result.passed else "red"
|
|
13
|
+
console.print(
|
|
14
|
+
f"[{color}][{status}][/{color}] {name} ({result.duration_seconds:.2f}s)"
|
|
15
|
+
)
|
|
16
|
+
diagnostics = "\n".join(
|
|
17
|
+
output.strip() for output in (result.stdout, result.stderr) if output.strip()
|
|
18
|
+
)
|
|
19
|
+
if diagnostics:
|
|
20
|
+
console.print(diagnostics, markup=False)
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from dataclasses import asdict
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from code_quality_guard.runner import CommandResult
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def render_json(results: dict[str, CommandResult]) -> str:
|
|
11
|
+
payload: dict[str, Any] = {
|
|
12
|
+
"checks": {name: asdict(result) for name, result in results.items()},
|
|
13
|
+
"passed": all(result.passed for result in results.values()),
|
|
14
|
+
}
|
|
15
|
+
return json.dumps(payload, indent=2, sort_keys=True)
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import subprocess
|
|
4
|
+
import time
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@dataclass(frozen=True)
|
|
10
|
+
class CommandResult:
|
|
11
|
+
command: list[str]
|
|
12
|
+
passed: bool
|
|
13
|
+
exit_code: int
|
|
14
|
+
stdout: str
|
|
15
|
+
stderr: str
|
|
16
|
+
duration_seconds: float
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class SubprocessRunner:
|
|
20
|
+
def run(self, command: list[str], cwd: Path | None = None) -> CommandResult:
|
|
21
|
+
started = time.perf_counter()
|
|
22
|
+
try:
|
|
23
|
+
completed = subprocess.run(
|
|
24
|
+
command, cwd=cwd, capture_output=True, text=True, check=False
|
|
25
|
+
)
|
|
26
|
+
stdout, stderr, exit_code = completed.stdout, completed.stderr, completed.returncode
|
|
27
|
+
except FileNotFoundError as error:
|
|
28
|
+
stdout, stderr, exit_code = (
|
|
29
|
+
"",
|
|
30
|
+
f"Command not found: {error.filename or command[0]}",
|
|
31
|
+
127,
|
|
32
|
+
)
|
|
33
|
+
return CommandResult(
|
|
34
|
+
command=command,
|
|
35
|
+
passed=exit_code == 0,
|
|
36
|
+
exit_code=exit_code,
|
|
37
|
+
stdout=stdout,
|
|
38
|
+
stderr=stderr,
|
|
39
|
+
duration_seconds=time.perf_counter() - started,
|
|
40
|
+
)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Adapters for external quality tools."""
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Protocol
|
|
4
|
+
|
|
5
|
+
from code_quality_guard.config.settings import Settings
|
|
6
|
+
from code_quality_guard.runner import CommandResult
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class Tool(Protocol):
|
|
10
|
+
name: str
|
|
11
|
+
|
|
12
|
+
def run(self, settings: Settings) -> CommandResult:
|
|
13
|
+
"""Run the tool for the configured target."""
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import shutil
|
|
4
|
+
import sys
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def resolve_executable(name: str) -> str:
|
|
9
|
+
environment_executable = Path(sys.executable).with_name(name)
|
|
10
|
+
if environment_executable.exists():
|
|
11
|
+
return str(environment_executable)
|
|
12
|
+
return shutil.which(name) or name
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from code_quality_guard.config.settings import Settings
|
|
4
|
+
from code_quality_guard.runner import CommandResult, SubprocessRunner
|
|
5
|
+
from code_quality_guard.tools.command import resolve_executable
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class MypyTool:
|
|
9
|
+
name = "mypy"
|
|
10
|
+
|
|
11
|
+
def __init__(self, runner: SubprocessRunner | None = None) -> None:
|
|
12
|
+
self.runner = runner or SubprocessRunner()
|
|
13
|
+
|
|
14
|
+
def run(self, settings: Settings) -> CommandResult:
|
|
15
|
+
command = [resolve_executable("mypy"), str(settings.target)]
|
|
16
|
+
command.extend(settings.tool_args.get(self.name, ()))
|
|
17
|
+
cwd = settings.target if settings.target.is_dir() else settings.target.parent
|
|
18
|
+
return self.runner.run(command, cwd=cwd)
|