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.
@@ -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())