epita-coding-style 2.3.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.
- epita_coding_style-2.3.0/.gitignore +40 -0
- epita_coding_style-2.3.0/PKG-INFO +118 -0
- epita_coding_style-2.3.0/README.md +88 -0
- epita_coding_style-2.3.0/pyproject.toml +65 -0
- epita_coding_style-2.3.0/src/epita_coding_style/.clang-format +79 -0
- epita_coding_style-2.3.0/src/epita_coding_style/__init__.py +20 -0
- epita_coding_style-2.3.0/src/epita_coding_style/checker.py +286 -0
- epita_coding_style-2.3.0/src/epita_coding_style/checks.py +372 -0
- epita_coding_style-2.3.0/src/epita_coding_style/config.py +181 -0
- epita_coding_style-2.3.0/src/epita_coding_style/core.py +84 -0
- epita_coding_style-2.3.0/tests/conftest.py +35 -0
- epita_coding_style-2.3.0/tests/integration/test_cli.bats +355 -0
- epita_coding_style-2.3.0/tests/integration/test_cli.sh +152 -0
- epita_coding_style-2.3.0/tests/test_braces_rules.py +127 -0
- epita_coding_style-2.3.0/tests/test_control_rules.py +49 -0
- epita_coding_style-2.3.0/tests/test_declaration_rules.py +29 -0
- epita_coding_style-2.3.0/tests/test_export_rules.py +166 -0
- epita_coding_style-2.3.0/tests/test_file_rules.py +45 -0
- epita_coding_style-2.3.0/tests/test_format_rules.py +76 -0
- epita_coding_style-2.3.0/tests/test_function_rules.py +75 -0
- epita_coding_style-2.3.0/tests/test_line_content.py +65 -0
- epita_coding_style-2.3.0/tests/test_preprocessor_rules.py +56 -0
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
.venv/
|
|
3
|
+
__pycache__/
|
|
4
|
+
*.py[cod]
|
|
5
|
+
*$py.class
|
|
6
|
+
*.so
|
|
7
|
+
.Python
|
|
8
|
+
build/
|
|
9
|
+
develop-eggs/
|
|
10
|
+
dist/
|
|
11
|
+
downloads/
|
|
12
|
+
eggs/
|
|
13
|
+
.eggs/
|
|
14
|
+
lib/
|
|
15
|
+
lib64/
|
|
16
|
+
parts/
|
|
17
|
+
sdist/
|
|
18
|
+
var/
|
|
19
|
+
wheels/
|
|
20
|
+
*.egg-info/
|
|
21
|
+
.installed.cfg
|
|
22
|
+
*.egg
|
|
23
|
+
|
|
24
|
+
# Testing
|
|
25
|
+
.pytest_cache/
|
|
26
|
+
.coverage
|
|
27
|
+
htmlcov/
|
|
28
|
+
.tox/
|
|
29
|
+
.nox/
|
|
30
|
+
|
|
31
|
+
# IDE
|
|
32
|
+
.idea/
|
|
33
|
+
.vscode/
|
|
34
|
+
*.swp
|
|
35
|
+
*.swo
|
|
36
|
+
*~
|
|
37
|
+
|
|
38
|
+
# OS
|
|
39
|
+
.DS_Store
|
|
40
|
+
Thumbs.db
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: epita-coding-style
|
|
3
|
+
Version: 2.3.0
|
|
4
|
+
Summary: EPITA C Coding Style Checker - validates C code against EPITA coding standards
|
|
5
|
+
Project-URL: Homepage, https://github.com/KazeTachinuu/coding-style
|
|
6
|
+
Project-URL: Repository, https://github.com/KazeTachinuu/coding-style
|
|
7
|
+
Project-URL: Issues, https://github.com/KazeTachinuu/coding-style/issues
|
|
8
|
+
Author: Hugo
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
Keywords: c,checker,coding-style,epita,linter
|
|
11
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
12
|
+
Classifier: Environment :: Console
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Intended Audience :: Education
|
|
15
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
16
|
+
Classifier: Operating System :: OS Independent
|
|
17
|
+
Classifier: Programming Language :: C
|
|
18
|
+
Classifier: Programming Language :: Python :: 3
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
22
|
+
Classifier: Topic :: Software Development :: Quality Assurance
|
|
23
|
+
Requires-Python: >=3.10
|
|
24
|
+
Requires-Dist: tomli>=2.0.0; python_version < '3.11'
|
|
25
|
+
Requires-Dist: tree-sitter-c>=0.23.0
|
|
26
|
+
Requires-Dist: tree-sitter>=0.23.0
|
|
27
|
+
Provides-Extra: dev
|
|
28
|
+
Requires-Dist: pytest>=8.0.0; extra == 'dev'
|
|
29
|
+
Description-Content-Type: text/markdown
|
|
30
|
+
|
|
31
|
+
# EPITA C Coding Style Checker
|
|
32
|
+
|
|
33
|
+
A fast C linter for EPITA coding style rules. Uses [tree-sitter](https://tree-sitter.github.io/) for robust AST-based parsing.
|
|
34
|
+
|
|
35
|
+
## Installation
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
pipx install epita-coding-style
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Quick Start
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
epita-coding-style src/ # Check files/directories
|
|
45
|
+
epita-coding-style --list-rules # List all rules with descriptions
|
|
46
|
+
epita-coding-style --show-config # Show current configuration
|
|
47
|
+
epita-coding-style --help # Full usage info
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Configuration
|
|
51
|
+
|
|
52
|
+
Configuration is auto-detected from (in order):
|
|
53
|
+
- `.epita-style`
|
|
54
|
+
- `.epita-style.toml`
|
|
55
|
+
- `epita-style.toml`
|
|
56
|
+
- `[tool.epita-coding-style]` in `pyproject.toml`
|
|
57
|
+
|
|
58
|
+
**Priority:** CLI flags > config file > preset > defaults
|
|
59
|
+
|
|
60
|
+
### Generate a Config File
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
epita-coding-style --show-config --no-color > .epita-style.toml
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
This outputs a complete, commented TOML config you can customize.
|
|
67
|
+
|
|
68
|
+
### Presets
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
epita-coding-style --preset 42sh src/ # 40 lines, goto/cast allowed
|
|
72
|
+
epita-coding-style --preset noformat src/ # Same + skip clang-format
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### Example Config
|
|
76
|
+
|
|
77
|
+
```toml
|
|
78
|
+
# .epita-style.toml
|
|
79
|
+
max_lines = 40
|
|
80
|
+
|
|
81
|
+
[rules]
|
|
82
|
+
"keyword.goto" = false # Allow goto
|
|
83
|
+
"cast" = false # Allow casts
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Or in `pyproject.toml`:
|
|
87
|
+
|
|
88
|
+
```toml
|
|
89
|
+
[tool.epita-coding-style]
|
|
90
|
+
max_lines = 40
|
|
91
|
+
|
|
92
|
+
[tool.epita-coding-style.rules]
|
|
93
|
+
"keyword.goto" = false
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## clang-format
|
|
97
|
+
|
|
98
|
+
The `format` rule uses `clang-format` to check code formatting. Requires `clang-format` to be installed.
|
|
99
|
+
|
|
100
|
+
The checker looks for `.clang-format` in the file's directory (walking up to root), or uses the bundled EPITA config.
|
|
101
|
+
|
|
102
|
+
To disable: set `"format" = false` in your config, or use `--preset noformat`.
|
|
103
|
+
|
|
104
|
+
## Pre-commit Hook
|
|
105
|
+
|
|
106
|
+
```yaml
|
|
107
|
+
# .pre-commit-config.yaml
|
|
108
|
+
repos:
|
|
109
|
+
- repo: https://github.com/KazeTachinuu/epita-coding-style
|
|
110
|
+
rev: v2.3.0
|
|
111
|
+
hooks:
|
|
112
|
+
- id: epita-coding-style
|
|
113
|
+
args: [--preset, 42sh] # optional
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## License
|
|
117
|
+
|
|
118
|
+
MIT
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# EPITA C Coding Style Checker
|
|
2
|
+
|
|
3
|
+
A fast C linter for EPITA coding style rules. Uses [tree-sitter](https://tree-sitter.github.io/) for robust AST-based parsing.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pipx install epita-coding-style
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Quick Start
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
epita-coding-style src/ # Check files/directories
|
|
15
|
+
epita-coding-style --list-rules # List all rules with descriptions
|
|
16
|
+
epita-coding-style --show-config # Show current configuration
|
|
17
|
+
epita-coding-style --help # Full usage info
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Configuration
|
|
21
|
+
|
|
22
|
+
Configuration is auto-detected from (in order):
|
|
23
|
+
- `.epita-style`
|
|
24
|
+
- `.epita-style.toml`
|
|
25
|
+
- `epita-style.toml`
|
|
26
|
+
- `[tool.epita-coding-style]` in `pyproject.toml`
|
|
27
|
+
|
|
28
|
+
**Priority:** CLI flags > config file > preset > defaults
|
|
29
|
+
|
|
30
|
+
### Generate a Config File
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
epita-coding-style --show-config --no-color > .epita-style.toml
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
This outputs a complete, commented TOML config you can customize.
|
|
37
|
+
|
|
38
|
+
### Presets
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
epita-coding-style --preset 42sh src/ # 40 lines, goto/cast allowed
|
|
42
|
+
epita-coding-style --preset noformat src/ # Same + skip clang-format
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### Example Config
|
|
46
|
+
|
|
47
|
+
```toml
|
|
48
|
+
# .epita-style.toml
|
|
49
|
+
max_lines = 40
|
|
50
|
+
|
|
51
|
+
[rules]
|
|
52
|
+
"keyword.goto" = false # Allow goto
|
|
53
|
+
"cast" = false # Allow casts
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Or in `pyproject.toml`:
|
|
57
|
+
|
|
58
|
+
```toml
|
|
59
|
+
[tool.epita-coding-style]
|
|
60
|
+
max_lines = 40
|
|
61
|
+
|
|
62
|
+
[tool.epita-coding-style.rules]
|
|
63
|
+
"keyword.goto" = false
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## clang-format
|
|
67
|
+
|
|
68
|
+
The `format` rule uses `clang-format` to check code formatting. Requires `clang-format` to be installed.
|
|
69
|
+
|
|
70
|
+
The checker looks for `.clang-format` in the file's directory (walking up to root), or uses the bundled EPITA config.
|
|
71
|
+
|
|
72
|
+
To disable: set `"format" = false` in your config, or use `--preset noformat`.
|
|
73
|
+
|
|
74
|
+
## Pre-commit Hook
|
|
75
|
+
|
|
76
|
+
```yaml
|
|
77
|
+
# .pre-commit-config.yaml
|
|
78
|
+
repos:
|
|
79
|
+
- repo: https://github.com/KazeTachinuu/epita-coding-style
|
|
80
|
+
rev: v2.3.0
|
|
81
|
+
hooks:
|
|
82
|
+
- id: epita-coding-style
|
|
83
|
+
args: [--preset, 42sh] # optional
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## License
|
|
87
|
+
|
|
88
|
+
MIT
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "epita-coding-style"
|
|
7
|
+
version = "2.3.0"
|
|
8
|
+
description = "EPITA C Coding Style Checker - validates C code against EPITA coding standards"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
license = "MIT"
|
|
12
|
+
authors = [
|
|
13
|
+
{ name = "Hugo" }
|
|
14
|
+
]
|
|
15
|
+
classifiers = [
|
|
16
|
+
"Development Status :: 5 - Production/Stable",
|
|
17
|
+
"Environment :: Console",
|
|
18
|
+
"Intended Audience :: Developers",
|
|
19
|
+
"Intended Audience :: Education",
|
|
20
|
+
"License :: OSI Approved :: MIT License",
|
|
21
|
+
"Operating System :: OS Independent",
|
|
22
|
+
"Programming Language :: Python :: 3",
|
|
23
|
+
"Programming Language :: Python :: 3.10",
|
|
24
|
+
"Programming Language :: Python :: 3.11",
|
|
25
|
+
"Programming Language :: Python :: 3.12",
|
|
26
|
+
"Programming Language :: C",
|
|
27
|
+
"Topic :: Software Development :: Quality Assurance",
|
|
28
|
+
]
|
|
29
|
+
keywords = ["c", "coding-style", "linter", "epita", "checker"]
|
|
30
|
+
dependencies = [
|
|
31
|
+
"tree-sitter>=0.23.0",
|
|
32
|
+
"tree-sitter-c>=0.23.0",
|
|
33
|
+
"tomli>=2.0.0; python_version < '3.11'",
|
|
34
|
+
]
|
|
35
|
+
|
|
36
|
+
[project.scripts]
|
|
37
|
+
epita-coding-style = "epita_coding_style:main"
|
|
38
|
+
|
|
39
|
+
[project.urls]
|
|
40
|
+
Homepage = "https://github.com/KazeTachinuu/coding-style"
|
|
41
|
+
Repository = "https://github.com/KazeTachinuu/coding-style"
|
|
42
|
+
Issues = "https://github.com/KazeTachinuu/coding-style/issues"
|
|
43
|
+
|
|
44
|
+
[project.optional-dependencies]
|
|
45
|
+
dev = [
|
|
46
|
+
"pytest>=8.0.0",
|
|
47
|
+
]
|
|
48
|
+
|
|
49
|
+
[tool.hatch.build.targets.wheel]
|
|
50
|
+
packages = ["src/epita_coding_style"]
|
|
51
|
+
include = ["src/epita_coding_style/.clang-format"]
|
|
52
|
+
|
|
53
|
+
[tool.hatch.build.targets.sdist]
|
|
54
|
+
include = [
|
|
55
|
+
"/src",
|
|
56
|
+
"/tests",
|
|
57
|
+
"/README.md",
|
|
58
|
+
]
|
|
59
|
+
|
|
60
|
+
[tool.pytest.ini_options]
|
|
61
|
+
testpaths = ["tests"]
|
|
62
|
+
pythonpath = ["src"]
|
|
63
|
+
python_files = ["test_*.py"]
|
|
64
|
+
python_functions = ["test_*"]
|
|
65
|
+
addopts = "-v"
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
AccessModifierOffset: -4
|
|
2
|
+
AlignAfterOpenBracket: Align
|
|
3
|
+
AlignConsecutiveAssignments: false
|
|
4
|
+
AlignConsecutiveDeclarations: false
|
|
5
|
+
AlignEscapedNewlines: Right
|
|
6
|
+
AlignOperands: false
|
|
7
|
+
AlignTrailingComments: false
|
|
8
|
+
AllowAllParametersOfDeclarationOnNextLine: false
|
|
9
|
+
AllowShortBlocksOnASingleLine: false
|
|
10
|
+
AllowShortCaseLabelsOnASingleLine: false
|
|
11
|
+
AllowShortFunctionsOnASingleLine: None
|
|
12
|
+
AllowShortIfStatementsOnASingleLine: false
|
|
13
|
+
AlwaysBreakAfterReturnType: None
|
|
14
|
+
AlwaysBreakBeforeMultilineStrings: false
|
|
15
|
+
AlwaysBreakTemplateDeclarations: Yes
|
|
16
|
+
BinPackArguments: true
|
|
17
|
+
BinPackParameters: true
|
|
18
|
+
BreakBeforeBraces: Custom
|
|
19
|
+
BraceWrapping:
|
|
20
|
+
AfterEnum: true
|
|
21
|
+
AfterClass: true
|
|
22
|
+
AfterControlStatement: true
|
|
23
|
+
AfterFunction: true
|
|
24
|
+
AfterNamespace: true
|
|
25
|
+
AfterStruct: true
|
|
26
|
+
AfterUnion: true
|
|
27
|
+
AfterExternBlock: true
|
|
28
|
+
BeforeCatch: true
|
|
29
|
+
BeforeElse: true
|
|
30
|
+
IndentBraces: false
|
|
31
|
+
SplitEmptyFunction: false
|
|
32
|
+
SplitEmptyRecord: false
|
|
33
|
+
SplitEmptyNamespace: false
|
|
34
|
+
BreakBeforeBinaryOperators: NonAssignment
|
|
35
|
+
BreakBeforeTernaryOperators: true
|
|
36
|
+
BreakConstructorInitializers: BeforeComma
|
|
37
|
+
BreakInheritanceList: BeforeComma
|
|
38
|
+
BreakStringLiterals: true
|
|
39
|
+
ColumnLimit: 80
|
|
40
|
+
CompactNamespaces: false
|
|
41
|
+
ConstructorInitializerAllOnOneLineOrOnePerLine: false
|
|
42
|
+
ConstructorInitializerIndentWidth: 4
|
|
43
|
+
Cpp11BracedListStyle: false
|
|
44
|
+
DerivePointerAlignment: false
|
|
45
|
+
FixNamespaceComments: true
|
|
46
|
+
ForEachMacros: ['ILIST_FOREACH', 'ILIST_FOREACH_ENTRY']
|
|
47
|
+
IncludeBlocks: Regroup
|
|
48
|
+
IncludeCategories:
|
|
49
|
+
- Regex: '<.*>'
|
|
50
|
+
Priority: 1
|
|
51
|
+
- Regex: '.*'
|
|
52
|
+
Priority: 2
|
|
53
|
+
IndentCaseLabels: false
|
|
54
|
+
IndentPPDirectives: AfterHash
|
|
55
|
+
IndentWidth: 4
|
|
56
|
+
IndentWrappedFunctionNames: false
|
|
57
|
+
KeepEmptyLinesAtTheStartOfBlocks: false
|
|
58
|
+
Language: Cpp
|
|
59
|
+
NamespaceIndentation: All
|
|
60
|
+
PointerAlignment: Right
|
|
61
|
+
ReflowComments: true
|
|
62
|
+
SortIncludes: true
|
|
63
|
+
SortUsingDeclarations: false
|
|
64
|
+
SpaceAfterCStyleCast: false
|
|
65
|
+
SpaceAfterTemplateKeyword: true
|
|
66
|
+
SpaceBeforeAssignmentOperators: true
|
|
67
|
+
SpaceBeforeCpp11BracedList: false
|
|
68
|
+
SpaceBeforeCtorInitializerColon: true
|
|
69
|
+
SpaceBeforeParens: ControlStatements
|
|
70
|
+
SpaceBeforeRangeBasedForLoopColon: true
|
|
71
|
+
SpaceInEmptyParentheses: false
|
|
72
|
+
SpacesBeforeTrailingComments: 1
|
|
73
|
+
SpacesInAngles: false
|
|
74
|
+
SpacesInCStyleCastParentheses: false
|
|
75
|
+
SpacesInContainerLiterals: false
|
|
76
|
+
SpacesInParentheses: false
|
|
77
|
+
SpacesInSquareBrackets: false
|
|
78
|
+
TabWidth: 4
|
|
79
|
+
UseTab: Never
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""EPITA C Coding Style Checker."""
|
|
2
|
+
|
|
3
|
+
from importlib.metadata import version
|
|
4
|
+
|
|
5
|
+
__version__ = version("epita-coding-style")
|
|
6
|
+
|
|
7
|
+
from .core import Violation, Severity
|
|
8
|
+
from .config import Config, load_config, PRESETS
|
|
9
|
+
from .checker import check_file, main
|
|
10
|
+
|
|
11
|
+
__all__ = [
|
|
12
|
+
"check_file",
|
|
13
|
+
"Violation",
|
|
14
|
+
"Severity",
|
|
15
|
+
"main",
|
|
16
|
+
"Config",
|
|
17
|
+
"load_config",
|
|
18
|
+
"PRESETS",
|
|
19
|
+
"__version__",
|
|
20
|
+
]
|
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""EPITA C Coding Style Checker - main entry point."""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
import argparse
|
|
7
|
+
import os
|
|
8
|
+
import sys
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
from . import __version__
|
|
12
|
+
from .config import Config, PRESETS, RULES_META, load_config
|
|
13
|
+
from .core import Violation, Severity, parse, NodeCache
|
|
14
|
+
from .checks import (
|
|
15
|
+
check_file_format,
|
|
16
|
+
check_braces,
|
|
17
|
+
check_functions,
|
|
18
|
+
check_exports,
|
|
19
|
+
check_preprocessor,
|
|
20
|
+
check_misc,
|
|
21
|
+
check_clang_format,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def check_file(path: str, cfg: Config) -> list[Violation]:
|
|
26
|
+
"""Run all checks on a file."""
|
|
27
|
+
try:
|
|
28
|
+
with open(path, 'r', encoding='utf-8', errors='replace', newline='') as f:
|
|
29
|
+
content = f.read()
|
|
30
|
+
except Exception as e:
|
|
31
|
+
return [Violation(path, 0, "file.read", str(e))]
|
|
32
|
+
|
|
33
|
+
lines = content.split('\n')
|
|
34
|
+
content_bytes = content.encode()
|
|
35
|
+
tree = parse(content_bytes)
|
|
36
|
+
nodes = NodeCache(tree)
|
|
37
|
+
|
|
38
|
+
return (
|
|
39
|
+
check_file_format(path, content, lines, cfg) +
|
|
40
|
+
check_braces(path, lines, cfg) +
|
|
41
|
+
check_functions(path, nodes, content_bytes, lines, cfg) +
|
|
42
|
+
check_exports(path, nodes, content_bytes, cfg) +
|
|
43
|
+
check_preprocessor(path, lines, cfg) +
|
|
44
|
+
check_misc(path, nodes, content_bytes, lines, cfg) +
|
|
45
|
+
check_clang_format(path, cfg)
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def find_files(paths: list[str]) -> list[str]:
|
|
50
|
+
"""Find all .c and .h files."""
|
|
51
|
+
files = []
|
|
52
|
+
for p in paths:
|
|
53
|
+
if os.path.isfile(p) and p.endswith(('.c', '.h')):
|
|
54
|
+
files.append(p)
|
|
55
|
+
elif os.path.isdir(p):
|
|
56
|
+
for root, _, names in os.walk(p):
|
|
57
|
+
files.extend(os.path.join(root, n) for n in names if n.endswith(('.c', '.h')))
|
|
58
|
+
return sorted(files)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _print_rules():
|
|
62
|
+
"""Print all rules grouped by category with descriptions."""
|
|
63
|
+
cfg = Config()
|
|
64
|
+
categories: dict[str, list[tuple[str, str, bool]]] = {}
|
|
65
|
+
|
|
66
|
+
for rule in sorted(cfg.rules.keys()):
|
|
67
|
+
desc, cat = RULES_META.get(rule, (rule, "Other"))
|
|
68
|
+
enabled = cfg.rules.get(rule, True)
|
|
69
|
+
categories.setdefault(cat, []).append((rule, desc, enabled))
|
|
70
|
+
|
|
71
|
+
cat_order = ["File", "Style", "Functions", "Exports", "Preprocessor",
|
|
72
|
+
"Declarations", "Control", "Strict", "Formatting", "Other"]
|
|
73
|
+
first = True
|
|
74
|
+
for cat in cat_order:
|
|
75
|
+
if cat not in categories:
|
|
76
|
+
continue
|
|
77
|
+
if not first:
|
|
78
|
+
print()
|
|
79
|
+
first = False
|
|
80
|
+
print(f"{cat}:")
|
|
81
|
+
for rule, desc, _ in categories[cat]:
|
|
82
|
+
print(f" {rule:<20} {desc}")
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def _print_config(cfg: Config, use_color: bool = True):
|
|
86
|
+
"""Print current effective configuration as valid TOML with comments."""
|
|
87
|
+
defaults = Config()
|
|
88
|
+
|
|
89
|
+
# Colors
|
|
90
|
+
DIM = '\033[2m' if use_color else ''
|
|
91
|
+
GREEN = '\033[32m' if use_color else ''
|
|
92
|
+
RED = '\033[31m' if use_color else ''
|
|
93
|
+
CYAN = '\033[36m' if use_color else ''
|
|
94
|
+
RST = '\033[0m' if use_color else ''
|
|
95
|
+
|
|
96
|
+
# Build all lines first to calculate alignment
|
|
97
|
+
limit_lines = [
|
|
98
|
+
("max_lines", cfg.max_lines, defaults.max_lines, "Max lines per function body"),
|
|
99
|
+
("max_args", cfg.max_args, defaults.max_args, "Max arguments per function"),
|
|
100
|
+
("max_funcs", cfg.max_funcs, defaults.max_funcs, "Max exported functions per file"),
|
|
101
|
+
("max_globals", cfg.max_globals, defaults.max_globals, "Max exported globals per file"),
|
|
102
|
+
]
|
|
103
|
+
|
|
104
|
+
# Rules by category
|
|
105
|
+
categories: dict[str, list[tuple[str, str, bool]]] = {}
|
|
106
|
+
for rule in sorted(cfg.rules.keys()):
|
|
107
|
+
desc, cat = RULES_META.get(rule, (rule, "Other"))
|
|
108
|
+
enabled = cfg.rules.get(rule, True)
|
|
109
|
+
categories.setdefault(cat, []).append((rule, desc, enabled))
|
|
110
|
+
|
|
111
|
+
# Calculate max widths
|
|
112
|
+
limit_width = max(len(f"{name} = {val}") for name, val, _, _ in limit_lines)
|
|
113
|
+
rule_width = max(len(f'"{rule}" = {str(en).lower()}') for rules in categories.values() for rule, _, en in rules)
|
|
114
|
+
col = max(limit_width, rule_width) + 2
|
|
115
|
+
|
|
116
|
+
print(f"{DIM}# Effective configuration (copy to .epita-style.toml){RST}")
|
|
117
|
+
print()
|
|
118
|
+
|
|
119
|
+
# Limits
|
|
120
|
+
print(f"{DIM}# Limits{RST}")
|
|
121
|
+
for name, val, default, desc in limit_lines:
|
|
122
|
+
code = f"{name} = {val}"
|
|
123
|
+
color = CYAN if val != default else ''
|
|
124
|
+
print(f"{color}{code:<{col}}{RST}{DIM}# {desc} (default: {default}){RST}")
|
|
125
|
+
|
|
126
|
+
print()
|
|
127
|
+
print(f"{DIM}# Rules: true = enabled, false = disabled{RST}")
|
|
128
|
+
print("[rules]")
|
|
129
|
+
|
|
130
|
+
for cat in ["File", "Style", "Functions", "Exports", "Preprocessor",
|
|
131
|
+
"Declarations", "Control", "Strict", "Formatting", "Other"]:
|
|
132
|
+
if cat not in categories:
|
|
133
|
+
continue
|
|
134
|
+
print(f"{DIM}# {cat}{RST}")
|
|
135
|
+
for rule, desc, enabled in categories[cat]:
|
|
136
|
+
val_str = "true" if enabled else "false"
|
|
137
|
+
code = f'"{rule}" = {val_str}'
|
|
138
|
+
color = GREEN if enabled else RED
|
|
139
|
+
print(f'{color}{code:<{col}}{RST}{DIM}# {desc}{RST}')
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def main():
|
|
143
|
+
# Build epilog with config file and preset info
|
|
144
|
+
epilog = """\
|
|
145
|
+
Configuration:
|
|
146
|
+
Auto-detected files (in order): .epita-style, .epita-style.toml,
|
|
147
|
+
epita-style.toml, or [tool.epita-coding-style] in pyproject.toml
|
|
148
|
+
|
|
149
|
+
Config file format (TOML):
|
|
150
|
+
max_lines = 40
|
|
151
|
+
[rules]
|
|
152
|
+
"keyword.goto" = false
|
|
153
|
+
|
|
154
|
+
Priority: CLI flags > config file > preset > defaults
|
|
155
|
+
|
|
156
|
+
Presets:
|
|
157
|
+
42sh max_lines=40, disables: goto, cast
|
|
158
|
+
noformat max_lines=40, disables: goto, cast, format
|
|
159
|
+
|
|
160
|
+
Exit codes:
|
|
161
|
+
0 No major violations
|
|
162
|
+
1 Major violations found or error
|
|
163
|
+
"""
|
|
164
|
+
|
|
165
|
+
ap = argparse.ArgumentParser(
|
|
166
|
+
prog='epita-coding-style',
|
|
167
|
+
description='Fast C linter for EPITA coding style compliance.',
|
|
168
|
+
epilog=epilog,
|
|
169
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
# Positional
|
|
173
|
+
ap.add_argument('paths', nargs='*', metavar='PATH',
|
|
174
|
+
help='files or directories to check (recursively finds .c/.h)')
|
|
175
|
+
|
|
176
|
+
# Config options
|
|
177
|
+
cfg_group = ap.add_argument_group('Config')
|
|
178
|
+
cfg_group.add_argument('--preset', choices=list(PRESETS.keys()), metavar='NAME',
|
|
179
|
+
help='use a preset: 42sh, noformat')
|
|
180
|
+
cfg_group.add_argument('--config', type=Path, metavar='FILE',
|
|
181
|
+
help='path to TOML config file')
|
|
182
|
+
|
|
183
|
+
# Limits
|
|
184
|
+
lim_group = ap.add_argument_group('Limits')
|
|
185
|
+
lim_group.add_argument('--max-lines', type=int, metavar='N',
|
|
186
|
+
help='max lines per function body [default: 30]')
|
|
187
|
+
lim_group.add_argument('--max-args', type=int, metavar='N',
|
|
188
|
+
help='max arguments per function [default: 4]')
|
|
189
|
+
lim_group.add_argument('--max-funcs', type=int, metavar='N',
|
|
190
|
+
help='max exported functions per file [default: 10]')
|
|
191
|
+
|
|
192
|
+
# Output
|
|
193
|
+
out_group = ap.add_argument_group('Output')
|
|
194
|
+
out_group.add_argument('-q', '--quiet', action='store_true',
|
|
195
|
+
help='only show summary')
|
|
196
|
+
out_group.add_argument('--no-color', action='store_true',
|
|
197
|
+
help='disable colored output')
|
|
198
|
+
|
|
199
|
+
# Info
|
|
200
|
+
info_group = ap.add_argument_group('Info')
|
|
201
|
+
info_group.add_argument('--list-rules', action='store_true',
|
|
202
|
+
help='list all rules with descriptions')
|
|
203
|
+
info_group.add_argument('--show-config', action='store_true',
|
|
204
|
+
help='show effective configuration and exit')
|
|
205
|
+
info_group.add_argument('-v', '--version', action='version',
|
|
206
|
+
version=f'%(prog)s {__version__}')
|
|
207
|
+
|
|
208
|
+
args = ap.parse_args()
|
|
209
|
+
|
|
210
|
+
# Info commands (no paths required)
|
|
211
|
+
if args.list_rules:
|
|
212
|
+
_print_rules()
|
|
213
|
+
return 0
|
|
214
|
+
|
|
215
|
+
if args.show_config:
|
|
216
|
+
cfg = load_config(
|
|
217
|
+
config_path=args.config,
|
|
218
|
+
preset=args.preset,
|
|
219
|
+
max_lines=args.max_lines,
|
|
220
|
+
max_args=args.max_args,
|
|
221
|
+
max_funcs=args.max_funcs,
|
|
222
|
+
)
|
|
223
|
+
_print_config(cfg, use_color=not args.no_color)
|
|
224
|
+
return 0
|
|
225
|
+
|
|
226
|
+
# Require paths for checking
|
|
227
|
+
if not args.paths:
|
|
228
|
+
ap.error("PATH is required")
|
|
229
|
+
|
|
230
|
+
# Load config
|
|
231
|
+
cfg = load_config(
|
|
232
|
+
config_path=args.config,
|
|
233
|
+
preset=args.preset,
|
|
234
|
+
max_lines=args.max_lines,
|
|
235
|
+
max_args=args.max_args,
|
|
236
|
+
max_funcs=args.max_funcs,
|
|
237
|
+
)
|
|
238
|
+
|
|
239
|
+
# Colors
|
|
240
|
+
R, Y, C, W, B, RST = ('\033[91m', '\033[93m', '\033[96m', '\033[97m', '\033[1m', '\033[0m')
|
|
241
|
+
if args.no_color:
|
|
242
|
+
R = Y = C = W = B = RST = ''
|
|
243
|
+
|
|
244
|
+
files = find_files(args.paths)
|
|
245
|
+
if not files:
|
|
246
|
+
print(f"{R}No C files found{RST}", file=sys.stderr)
|
|
247
|
+
return 1
|
|
248
|
+
|
|
249
|
+
total_major = total_minor = 0
|
|
250
|
+
files_needing_format = []
|
|
251
|
+
|
|
252
|
+
for path in files:
|
|
253
|
+
violations = check_file(path, cfg)
|
|
254
|
+
|
|
255
|
+
major = sum(1 for v in violations if v.severity == Severity.MAJOR)
|
|
256
|
+
minor = sum(1 for v in violations if v.severity == Severity.MINOR)
|
|
257
|
+
total_major += major
|
|
258
|
+
total_minor += minor
|
|
259
|
+
|
|
260
|
+
# Track files needing clang-format
|
|
261
|
+
if any(v.rule == "format" for v in violations):
|
|
262
|
+
files_needing_format.append(path)
|
|
263
|
+
|
|
264
|
+
if not args.quiet and violations:
|
|
265
|
+
for v in violations:
|
|
266
|
+
color = R if v.severity == Severity.MAJOR else Y
|
|
267
|
+
col_str = f":{v.column + 1}" if v.column is not None else ":1"
|
|
268
|
+
severity_word = "error" if v.severity == Severity.MAJOR else "warning"
|
|
269
|
+
print(f"{color}{path}:{v.line}{col_str}: {severity_word}: {v.message} [epita-{v.rule}]{RST}")
|
|
270
|
+
if v.line_content is not None:
|
|
271
|
+
print(f"{v.line_content}")
|
|
272
|
+
if v.column is not None:
|
|
273
|
+
print(f"{' ' * v.column}{color}^{RST}")
|
|
274
|
+
|
|
275
|
+
# Summary
|
|
276
|
+
print(f"\n{W}Files: {len(files)} Major: {R}{total_major}{RST} Minor: {Y}{total_minor}{RST}")
|
|
277
|
+
|
|
278
|
+
# Show clang-format command if there are files to format
|
|
279
|
+
if files_needing_format:
|
|
280
|
+
print(f"\n{Y}Fix formatting:{RST} clang-format -i {' '.join(files_needing_format)}")
|
|
281
|
+
|
|
282
|
+
return 1 if total_major > 0 else 0
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
if __name__ == '__main__':
|
|
286
|
+
sys.exit(main())
|