hard-lint-py 0.0.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,267 @@
1
+ Metadata-Version: 2.4
2
+ Name: hard-lint-py
3
+ Version: 0.0.0
4
+ Summary: Rigorous linting and code quality setup for Python projects with pre-commit hooks
5
+ License: MIT
6
+ Keywords: linting,pre-commit,code-quality,ruff,black,isort,coverage
7
+ Author: Naylson Ferreira
8
+ Author-email: naylsonfsa@gmail.com
9
+ Requires-Python: >=3.10
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.10
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Requires-Dist: black (>=23.0.0)
18
+ Requires-Dist: isort (>=5.12.0)
19
+ Requires-Dist: pre-commit (>=3.0.0)
20
+ Requires-Dist: ruff (>=0.1.0)
21
+ Project-URL: Homepage, https://github.com/naylsonferreira/hard-lint-py
22
+ Project-URL: Issues, https://github.com/naylsonferreira/hard-lint-py/issues
23
+ Project-URL: Repository, https://github.com/naylsonferreira/hard-lint-py.git
24
+ Description-Content-Type: text/markdown
25
+
26
+ # Hard-Lint for Python 🐍
27
+
28
+ Rigorous linting and code quality setup for Python projects with automatic pre-commit hooks.
29
+
30
+ ## Features
31
+
32
+ - **Automated Pre-commit Hooks**: Validates code before every commit
33
+ - `pre-commit`: Runs `ruff`, `black`, and `isort`
34
+ - `commit-msg`: Validates messages follow Conventional Commits format
35
+ - **Strict Linting**: Ruff checks for errors, complexity, and code quality
36
+ - **Synchronized Formatters**: Ruff, Black, and isort configured with same profile (no conflicts)
37
+ - **Zero Configuration**: Works out-of-the-box with sensible defaults
38
+ - **Multiplattform**: Works on Windows, macOS, and Linux
39
+ - **Auto-setup**: Single command to enable all checks
40
+
41
+ ## Installation
42
+
43
+ ```bash
44
+ pip install hard-lint-py
45
+ # or with poetry
46
+ poetry add -D hard-lint-py
47
+ ```
48
+
49
+ ## Quick Start
50
+
51
+ ```bash
52
+ # 1. Install and setup hooks
53
+ hard-lint-py
54
+
55
+ # 2. Make your first commit
56
+ git add .
57
+ git commit -m "feat: initial setup"
58
+ ```
59
+
60
+ That's it! Your project now has:
61
+ - **Ruff** for fast Python linting
62
+ - **Black** for code formatting
63
+ - **isort** for import sorting
64
+ - **Commitlint** for commit message validation
65
+
66
+ ## What Gets Installed
67
+
68
+ ### Git Hooks
69
+
70
+ Hooks are created in `.hardlint/_/`:
71
+
72
+ - **pre-commit**: Runs before commits to validate code
73
+ - Fixes issues with `ruff check --fix`
74
+ - Formats with `black`
75
+ - Sorts imports with `isort`
76
+ - Validates no comments exist
77
+
78
+ - **commit-msg**: Validates commit messages
79
+ - Must follow Conventional Commits format
80
+ - Examples: `feat:`, `fix:`, `chore:`, `docs:`, etc.
81
+
82
+ ## Linting Rules
83
+
84
+ ### Ruff Rules Enforced
85
+
86
+ | Rule | Category | Description |
87
+ |------|----------|-----------|
88
+ | E | pycodestyle errors | PEP 8 compliance |
89
+ | F | Pyflakes | Unused imports, undefined names |
90
+ | W | pycodestyle warnings | Code warnings |
91
+ | I | isort | Import sorting |
92
+ | N | pep8-naming | Naming conventions |
93
+ | C | mccabe | Code complexity |
94
+ | B | flake8-bugbear | Common bugs and design problems |
95
+ | RUF | Ruff-specific | Additional quality checks |
96
+ | UP | pyupgrade | Modernize Python syntax |
97
+
98
+ ### Synchronized Formatter Configuration
99
+
100
+ All three formatters (Ruff, Black, isort) use the **same profile** to prevent conflicts:
101
+
102
+ | Aspect | Configuration | Notes |
103
+ |--------|---------------|-------|
104
+ | **Line Length** | 100 characters | Consistent across all tools |
105
+ | **Python Version** | 3.10+ | Targets modern Python |
106
+ | **Imports Profile** | `isort: black` | Compatible with Black |
107
+ | **Trailing Commas** | Enabled | Multi-line consistency |
108
+
109
+ ### Configuration
110
+
111
+ Auto-configured in `pyproject.toml`:
112
+
113
+ ```toml
114
+ [tool.black]
115
+ line-length = 100
116
+ target-version = ["py310", "py311", "py312"]
117
+
118
+ [tool.isort]
119
+ profile = "black" # Compatible with Black
120
+ line_length = 100
121
+ known_first_party = ["hard_lint_py"]
122
+
123
+ [tool.ruff]
124
+ line-length = 100
125
+ target-version = "py310"
126
+ select = ["E", "F", "W", "I", "N", "C", "B", "RUF", "UP"]
127
+ ```
128
+
129
+ ## Usage
130
+
131
+ ### Normal workflow
132
+
133
+ ```bash
134
+ # Make changes
135
+ cat > new_file.py << 'EOF'
136
+ def greet(name):
137
+ # Greet a person by name
138
+ return f"Hello, {name}!"
139
+ EOF
140
+
141
+ # Stage changes
142
+ git add new_file.py
143
+
144
+ # Commit (hooks will run automatically)
145
+ git commit -m "feat: add greeting function"
146
+ ```
147
+
148
+ ### Pre-commit Hook Behavior
149
+
150
+ When you commit, the hook automatically:
151
+ 1. **Ruff**: Checks for linting issues, imports, and code quality
152
+ 2. **Black**: Formats code to 100-character lines
153
+ 3. **isort**: Sorts and organizes imports
154
+ 4. Blocks commit if any issues can't be auto-fixed
155
+
156
+ If pre-commit fails:
157
+ ```bash
158
+ # Fix will be auto-applied, just re-add and commit
159
+ git add .
160
+ git commit -m "feat: your message"
161
+ ```
162
+
163
+ ### If commit-msg fails
164
+
165
+ Invalid message example:
166
+ ```bash
167
+ git commit -m "added this feature" # ❌ Missing type prefix
168
+ ```
169
+
170
+ Valid message example:
171
+ ```bash
172
+ git commit -m "feat: added new feature" # ✅ Has type prefix
173
+ ```
174
+
175
+ ### Skip hooks (use with caution)
176
+
177
+ ```bash
178
+ git commit -m "..." --no-verify
179
+ ```
180
+
181
+ ## Configuration
182
+
183
+ All tools read from `pyproject.toml`. Customize as needed:
184
+
185
+ ```toml
186
+ [tool.ruff]
187
+ line-length = 120
188
+ select = ["E", "F", "W"]
189
+
190
+ [tool.black]
191
+ line-length = 120
192
+
193
+ [tool.isort]
194
+ profile = "black"
195
+ line_length = 120
196
+ ```
197
+
198
+ ## Troubleshooting
199
+
200
+ **Hooks not running?**
201
+ ```bash
202
+ # Re-run installation
203
+ hard-lint-py
204
+ ```
205
+
206
+
207
+
208
+ **Commit-msg validation failing?**
209
+ - Check that your message starts with a type: `feat:`, `fix:`, `chore:`, etc.
210
+
211
+ **Formatters conflicting with each other?**
212
+ - They shouldn't! Ruff, Black, and isort are configured with the same profile
213
+ - If you see conflicts, run all three: `ruff check --fix`, `black .`, `isort .`
214
+
215
+ **Line length conflicts?**
216
+ - All tools configured to 100 characters
217
+ - Black and isort won't fight over formatting
218
+ - Ruff respects Black's decisions (E501 ignored)
219
+
220
+ ## Supported Python Versions
221
+
222
+ - Python 3.10+
223
+ - Python 3.11
224
+ - Python 3.12
225
+
226
+ ## Development
227
+
228
+ When developing with hard-lint-py:
229
+
230
+ ### Pre-commit Validation
231
+ Your code will be validated on every commit:
232
+ ```bash
233
+ git commit -m "feat: your feature"
234
+ # Runs: ruff check --fix → black → isort → commitlint
235
+ ```
236
+
237
+ ### Manual Validation
238
+ Run validation checks manually:
239
+ ```bash
240
+ # All checks
241
+ make lint
242
+ make format
243
+ make test
244
+ make test-cov
245
+
246
+ # Individual tools
247
+ poetry run ruff check src/ tests/
248
+ poetry run black --check src/ tests/
249
+ poetry run isort --check-only src/ tests/
250
+ ```
251
+
252
+ ### Quality Standards
253
+ - ✅ 99% code coverage
254
+ - ✅ No unused imports or variables
255
+ - ✅ Consistent formatting (100 char lines)
256
+ - ✅ Code complexity within limits
257
+ - ✅ Modern Python syntax
258
+ - ✅ No common bugs or design problems
259
+
260
+ ## License
261
+
262
+ MIT
263
+
264
+ ## Author
265
+
266
+ Naylson Ferreira
267
+
@@ -0,0 +1,241 @@
1
+ # Hard-Lint for Python 🐍
2
+
3
+ Rigorous linting and code quality setup for Python projects with automatic pre-commit hooks.
4
+
5
+ ## Features
6
+
7
+ - **Automated Pre-commit Hooks**: Validates code before every commit
8
+ - `pre-commit`: Runs `ruff`, `black`, and `isort`
9
+ - `commit-msg`: Validates messages follow Conventional Commits format
10
+ - **Strict Linting**: Ruff checks for errors, complexity, and code quality
11
+ - **Synchronized Formatters**: Ruff, Black, and isort configured with same profile (no conflicts)
12
+ - **Zero Configuration**: Works out-of-the-box with sensible defaults
13
+ - **Multiplattform**: Works on Windows, macOS, and Linux
14
+ - **Auto-setup**: Single command to enable all checks
15
+
16
+ ## Installation
17
+
18
+ ```bash
19
+ pip install hard-lint-py
20
+ # or with poetry
21
+ poetry add -D hard-lint-py
22
+ ```
23
+
24
+ ## Quick Start
25
+
26
+ ```bash
27
+ # 1. Install and setup hooks
28
+ hard-lint-py
29
+
30
+ # 2. Make your first commit
31
+ git add .
32
+ git commit -m "feat: initial setup"
33
+ ```
34
+
35
+ That's it! Your project now has:
36
+ - **Ruff** for fast Python linting
37
+ - **Black** for code formatting
38
+ - **isort** for import sorting
39
+ - **Commitlint** for commit message validation
40
+
41
+ ## What Gets Installed
42
+
43
+ ### Git Hooks
44
+
45
+ Hooks are created in `.hardlint/_/`:
46
+
47
+ - **pre-commit**: Runs before commits to validate code
48
+ - Fixes issues with `ruff check --fix`
49
+ - Formats with `black`
50
+ - Sorts imports with `isort`
51
+ - Validates no comments exist
52
+
53
+ - **commit-msg**: Validates commit messages
54
+ - Must follow Conventional Commits format
55
+ - Examples: `feat:`, `fix:`, `chore:`, `docs:`, etc.
56
+
57
+ ## Linting Rules
58
+
59
+ ### Ruff Rules Enforced
60
+
61
+ | Rule | Category | Description |
62
+ |------|----------|-----------|
63
+ | E | pycodestyle errors | PEP 8 compliance |
64
+ | F | Pyflakes | Unused imports, undefined names |
65
+ | W | pycodestyle warnings | Code warnings |
66
+ | I | isort | Import sorting |
67
+ | N | pep8-naming | Naming conventions |
68
+ | C | mccabe | Code complexity |
69
+ | B | flake8-bugbear | Common bugs and design problems |
70
+ | RUF | Ruff-specific | Additional quality checks |
71
+ | UP | pyupgrade | Modernize Python syntax |
72
+
73
+ ### Synchronized Formatter Configuration
74
+
75
+ All three formatters (Ruff, Black, isort) use the **same profile** to prevent conflicts:
76
+
77
+ | Aspect | Configuration | Notes |
78
+ |--------|---------------|-------|
79
+ | **Line Length** | 100 characters | Consistent across all tools |
80
+ | **Python Version** | 3.10+ | Targets modern Python |
81
+ | **Imports Profile** | `isort: black` | Compatible with Black |
82
+ | **Trailing Commas** | Enabled | Multi-line consistency |
83
+
84
+ ### Configuration
85
+
86
+ Auto-configured in `pyproject.toml`:
87
+
88
+ ```toml
89
+ [tool.black]
90
+ line-length = 100
91
+ target-version = ["py310", "py311", "py312"]
92
+
93
+ [tool.isort]
94
+ profile = "black" # Compatible with Black
95
+ line_length = 100
96
+ known_first_party = ["hard_lint_py"]
97
+
98
+ [tool.ruff]
99
+ line-length = 100
100
+ target-version = "py310"
101
+ select = ["E", "F", "W", "I", "N", "C", "B", "RUF", "UP"]
102
+ ```
103
+
104
+ ## Usage
105
+
106
+ ### Normal workflow
107
+
108
+ ```bash
109
+ # Make changes
110
+ cat > new_file.py << 'EOF'
111
+ def greet(name):
112
+ # Greet a person by name
113
+ return f"Hello, {name}!"
114
+ EOF
115
+
116
+ # Stage changes
117
+ git add new_file.py
118
+
119
+ # Commit (hooks will run automatically)
120
+ git commit -m "feat: add greeting function"
121
+ ```
122
+
123
+ ### Pre-commit Hook Behavior
124
+
125
+ When you commit, the hook automatically:
126
+ 1. **Ruff**: Checks for linting issues, imports, and code quality
127
+ 2. **Black**: Formats code to 100-character lines
128
+ 3. **isort**: Sorts and organizes imports
129
+ 4. Blocks commit if any issues can't be auto-fixed
130
+
131
+ If pre-commit fails:
132
+ ```bash
133
+ # Fix will be auto-applied, just re-add and commit
134
+ git add .
135
+ git commit -m "feat: your message"
136
+ ```
137
+
138
+ ### If commit-msg fails
139
+
140
+ Invalid message example:
141
+ ```bash
142
+ git commit -m "added this feature" # ❌ Missing type prefix
143
+ ```
144
+
145
+ Valid message example:
146
+ ```bash
147
+ git commit -m "feat: added new feature" # ✅ Has type prefix
148
+ ```
149
+
150
+ ### Skip hooks (use with caution)
151
+
152
+ ```bash
153
+ git commit -m "..." --no-verify
154
+ ```
155
+
156
+ ## Configuration
157
+
158
+ All tools read from `pyproject.toml`. Customize as needed:
159
+
160
+ ```toml
161
+ [tool.ruff]
162
+ line-length = 120
163
+ select = ["E", "F", "W"]
164
+
165
+ [tool.black]
166
+ line-length = 120
167
+
168
+ [tool.isort]
169
+ profile = "black"
170
+ line_length = 120
171
+ ```
172
+
173
+ ## Troubleshooting
174
+
175
+ **Hooks not running?**
176
+ ```bash
177
+ # Re-run installation
178
+ hard-lint-py
179
+ ```
180
+
181
+
182
+
183
+ **Commit-msg validation failing?**
184
+ - Check that your message starts with a type: `feat:`, `fix:`, `chore:`, etc.
185
+
186
+ **Formatters conflicting with each other?**
187
+ - They shouldn't! Ruff, Black, and isort are configured with the same profile
188
+ - If you see conflicts, run all three: `ruff check --fix`, `black .`, `isort .`
189
+
190
+ **Line length conflicts?**
191
+ - All tools configured to 100 characters
192
+ - Black and isort won't fight over formatting
193
+ - Ruff respects Black's decisions (E501 ignored)
194
+
195
+ ## Supported Python Versions
196
+
197
+ - Python 3.10+
198
+ - Python 3.11
199
+ - Python 3.12
200
+
201
+ ## Development
202
+
203
+ When developing with hard-lint-py:
204
+
205
+ ### Pre-commit Validation
206
+ Your code will be validated on every commit:
207
+ ```bash
208
+ git commit -m "feat: your feature"
209
+ # Runs: ruff check --fix → black → isort → commitlint
210
+ ```
211
+
212
+ ### Manual Validation
213
+ Run validation checks manually:
214
+ ```bash
215
+ # All checks
216
+ make lint
217
+ make format
218
+ make test
219
+ make test-cov
220
+
221
+ # Individual tools
222
+ poetry run ruff check src/ tests/
223
+ poetry run black --check src/ tests/
224
+ poetry run isort --check-only src/ tests/
225
+ ```
226
+
227
+ ### Quality Standards
228
+ - ✅ 99% code coverage
229
+ - ✅ No unused imports or variables
230
+ - ✅ Consistent formatting (100 char lines)
231
+ - ✅ Code complexity within limits
232
+ - ✅ Modern Python syntax
233
+ - ✅ No common bugs or design problems
234
+
235
+ ## License
236
+
237
+ MIT
238
+
239
+ ## Author
240
+
241
+ Naylson Ferreira
@@ -0,0 +1,127 @@
1
+ [project]
2
+ name = "hard-lint-py"
3
+ version = "0.0.0"
4
+ description = "Rigorous linting and code quality setup for Python projects with pre-commit hooks"
5
+ authors = [
6
+ {name = "Naylson Ferreira", email = "naylsonfsa@gmail.com"}
7
+ ]
8
+ readme = "README.md"
9
+ requires-python = ">=3.10"
10
+ license = {text = "MIT"}
11
+ keywords = ["linting", "pre-commit", "code-quality", "ruff", "black", "isort", "coverage"]
12
+ classifiers = [
13
+ "Development Status :: 3 - Alpha",
14
+ "Intended Audience :: Developers",
15
+ "License :: OSI Approved :: MIT License",
16
+ "Programming Language :: Python :: 3",
17
+ "Programming Language :: Python :: 3.10",
18
+ "Programming Language :: Python :: 3.11",
19
+ "Programming Language :: Python :: 3.12",
20
+ ]
21
+ dependencies = [
22
+ "ruff>=0.1.0",
23
+ "black>=23.0.0",
24
+ "isort>=5.12.0",
25
+ "pre-commit>=3.0.0",
26
+ ]
27
+
28
+ [project.urls]
29
+ Homepage = "https://github.com/naylsonferreira/hard-lint-py"
30
+ Repository = "https://github.com/naylsonferreira/hard-lint-py.git"
31
+ Issues = "https://github.com/naylsonferreira/hard-lint-py/issues"
32
+
33
+ [project.scripts]
34
+ hard-lint-py = "hard_lint_py.cli:main"
35
+
36
+ [tool.poetry]
37
+ packages = [{include = "hard_lint_py", from = "src"}]
38
+
39
+ [tool.poetry.dependencies]
40
+ python = "^3.10"
41
+ ruff = "^0.1.0"
42
+ black = "^23.0.0"
43
+ isort = "^5.12.0"
44
+ pre-commit = "^3.0.0"
45
+
46
+ [tool.poetry.group.dev.dependencies]
47
+ pytest = "^7.4.0"
48
+ pytest-cov = "^4.1.0"
49
+ coverage = "^7.0.0"
50
+ mypy = "^1.5.0"
51
+ python-semantic-release = "^9.0.0"
52
+
53
+ [tool.black]
54
+ line-length = 100
55
+ target-version = ["py310", "py311", "py312"]
56
+ preview = true
57
+
58
+ [tool.isort]
59
+ profile = "black"
60
+ line_length = 100
61
+ include_trailing_comma = true
62
+ use_parentheses = true
63
+ ensure_newline_before_comments = true
64
+ balanced_wrapping = true
65
+ lines_after_imports = 2
66
+ skip_gitignore = true
67
+ known_first_party = ["hard_lint_py"]
68
+
69
+ [tool.ruff]
70
+ line-length = 100
71
+ target-version = "py310"
72
+ select = [
73
+ "E", # pycodestyle errors
74
+ "F", # Pyflakes (unused imports)
75
+ "W", # pycodestyle warnings
76
+ "I", # isort (import sorting)
77
+ "N", # pep8-naming
78
+ "C", # mccabe complexity
79
+ "B", # flake8-bugbear
80
+ "T", # flake8-print (prohibit print statements)
81
+ "RUF", # Ruff-specific rules
82
+ "UP", # pyupgrade (modernize code)
83
+ ]
84
+ ignore = [
85
+ "E501", # Line too long (Black handles this)
86
+ "I001", # Import sorting conflict with isort (let isort handle this)
87
+ "RUF001", # Ambiguous unicode character (too strict)
88
+ "T201", # Allow print statements (CLI tool)
89
+ ]
90
+
91
+ [tool.ruff.per-file-ignores]
92
+ # Tests excluded due to import complexity
93
+ "tests/*" = ["I001"]
94
+
95
+ [tool.pytest.ini_options]
96
+ testpaths = ["tests"]
97
+ addopts = "--cov=src/hard_lint_py --cov-report=term-missing --strict-markers"
98
+ markers = [
99
+ "unit: Unit tests",
100
+ "integration: Integration tests",
101
+ ]
102
+
103
+ [tool.mypy]
104
+ python_version = "3.10"
105
+ strict = true
106
+
107
+
108
+ [tool.semantic_release]
109
+ version_variable = "src/hard_lint_py/__init__.py:__version__"
110
+ version_toml = ["pyproject.toml:project.version"]
111
+ major_on_zero = false
112
+ build_command = "pip install build && python -m build"
113
+
114
+ [tool.semantic_release.branches.main]
115
+ match = "main"
116
+
117
+ [tool.semantic_release.changelog]
118
+ template_dir = "templates"
119
+ exclude_commit_patterns = []
120
+
121
+ [tool.semantic_release.publish]
122
+ dist_glob_patterns = ["dist/*"]
123
+ upload_to_vcs_release = true
124
+
125
+ [build-system]
126
+ requires = ["poetry-core>=2.0.0,<3.0.0"]
127
+ build-backend = "poetry.core.masonry.api"
@@ -0,0 +1,6 @@
1
+ from hard_lint_py.cli import main
2
+
3
+
4
+ __version__ = "0.2.2"
5
+ __author__ = "Naylson Ferreira"
6
+ __all__ = ["main"]
@@ -0,0 +1,141 @@
1
+ import argparse
2
+ import subprocess
3
+ import sys
4
+ from pathlib import Path
5
+
6
+ from hard_lint_py.installer import HardLintInstaller
7
+
8
+
9
+ def check_install_status():
10
+ """Check if hard-lint-py install was run and show warning if not."""
11
+ hardlint_dir = Path.cwd() / ".hardlint" / "_"
12
+ if not hardlint_dir.exists():
13
+ print("WARNING: Pre-commit hooks not installed!")
14
+ print("Run 'hard-lint-py install' to set up git hooks.")
15
+ print("(You can still use check/format commands without it)\n")
16
+
17
+
18
+ def run_check(paths=None) -> int:
19
+ check_install_status()
20
+ target_paths = paths if paths else ["."]
21
+ try:
22
+ print(f"Running checks on {target_paths}...")
23
+ subprocess.run(["poetry", "run", "ruff", "check", *target_paths], check=True)
24
+ subprocess.run(
25
+ ["poetry", "run", "black", "--check", "--line-length", "100", *target_paths],
26
+ check=True,
27
+ )
28
+ subprocess.run(
29
+ [
30
+ "poetry",
31
+ "run",
32
+ "isort",
33
+ "--check-only",
34
+ "--profile",
35
+ "black",
36
+ "--line-length",
37
+ "100",
38
+ *target_paths,
39
+ ],
40
+ check=True,
41
+ )
42
+ print("All checks passed!")
43
+ return 0
44
+ except subprocess.CalledProcessError:
45
+ print("Checks failed.")
46
+ return 1
47
+
48
+
49
+ def run_format(paths=None) -> int:
50
+ check_install_status()
51
+ target_paths = paths if paths else ["."]
52
+ try:
53
+ print(f"Running formatting on {target_paths}...")
54
+ subprocess.run(
55
+ ["poetry", "run", "black", "--line-length", "100", *target_paths], check=True
56
+ )
57
+ subprocess.run(
58
+ [
59
+ "poetry",
60
+ "run",
61
+ "isort",
62
+ "--profile",
63
+ "black",
64
+ "--line-length",
65
+ "100",
66
+ *target_paths,
67
+ ],
68
+ check=True,
69
+ )
70
+ subprocess.run(["poetry", "run", "ruff", "check", "--fix", *target_paths], check=True)
71
+ print("Formatting complete!")
72
+ return 0
73
+ except subprocess.CalledProcessError:
74
+ print("Formatting failed.")
75
+ return 1
76
+
77
+
78
+ def run_verify(paths=None) -> int:
79
+ try:
80
+ print("Running verification (lint + tests)...")
81
+ if run_check(paths) != 0:
82
+ print("Lint checks failed. Aborting verification.")
83
+ return 1
84
+
85
+ print("Running tests...")
86
+ subprocess.run(["poetry", "run", "pytest"], check=True)
87
+ print("Verification complete! All checks and tests passed.")
88
+ return 0
89
+ except subprocess.CalledProcessError:
90
+ print("Tests failed.")
91
+ return 1
92
+
93
+
94
+ def main() -> int:
95
+ parser = argparse.ArgumentParser(description="Hard Lint CLI")
96
+ subparsers = parser.add_subparsers(dest="command")
97
+
98
+ subparsers.add_parser("install", help="Install hardlint configuration")
99
+
100
+ check_parser = subparsers.add_parser("check", help="Run lint checks")
101
+ check_parser.add_argument("paths", nargs="*", help="Paths to check")
102
+
103
+ format_parser = subparsers.add_parser("format", help="Run formatting")
104
+ format_parser.add_argument("paths", nargs="*", help="Paths to format")
105
+
106
+ verify_parser = subparsers.add_parser("verify", help="Run verification (lint + tests)")
107
+ verify_parser.add_argument("paths", nargs="*", help="Paths to verify (passed to lint check)")
108
+
109
+ if len(sys.argv) == 1:
110
+ try:
111
+ installer = HardLintInstaller(Path.cwd())
112
+ installer.install()
113
+ print("Hardlint configuration installed successfully.")
114
+ return 0
115
+ except Exception as e:
116
+ print(f"Installation failed: {e}")
117
+ return 1
118
+
119
+ args = parser.parse_args()
120
+
121
+ if args.command == "install":
122
+ try:
123
+ installer = HardLintInstaller(Path.cwd())
124
+ installer.install()
125
+ print("Hardlint configuration installed successfully.")
126
+ return 0
127
+ except Exception as e:
128
+ print(f"Installation failed: {e}")
129
+ return 1
130
+ elif args.command == "check":
131
+ return run_check(args.paths)
132
+ elif args.command == "format":
133
+ return run_format(args.paths)
134
+ elif args.command == "verify":
135
+ return run_verify(args.paths)
136
+
137
+ return 0
138
+
139
+
140
+ if __name__ == "__main__":
141
+ sys.exit(main())
@@ -0,0 +1,84 @@
1
+ import subprocess
2
+ from pathlib import Path
3
+
4
+
5
+ class HardLintInstaller:
6
+ def __init__(self, project_root: Path):
7
+ self.project_root = project_root
8
+ self.git_dir = project_root / ".git"
9
+ self.pre_commit_dir = project_root / ".hardlint"
10
+ self.hooks_dir = self.pre_commit_dir / "_"
11
+ self.pyproject_path = project_root / "pyproject.toml"
12
+
13
+ def install(self) -> None:
14
+ if not self._check_git_repo():
15
+ raise RuntimeError("Not a Git repository. Initialize Git first with: git init")
16
+
17
+ if not self._check_pyproject():
18
+ raise RuntimeError("pyproject.toml not found in project root")
19
+
20
+ self._setup_pre_commit_hooks()
21
+ self._configure_git_hooks_path()
22
+ self._ensure_pyproject_config()
23
+
24
+ def _check_git_repo(self) -> bool:
25
+ return self.git_dir.exists()
26
+
27
+ def _check_pyproject(self) -> bool:
28
+ return self.pyproject_path.exists()
29
+
30
+ def _setup_pre_commit_hooks(self) -> None:
31
+ self.hooks_dir.mkdir(parents=True, exist_ok=True)
32
+
33
+ (self.pre_commit_dir / ".gitignore").write_text("*")
34
+
35
+ pre_commit_content = (
36
+ '#!/bin/sh\ncd "$(git rev-parse --show-toplevel)"\npoetry run hard-lint-py format .\n'
37
+ )
38
+ pre_commit_path = self.hooks_dir / "pre-commit"
39
+ pre_commit_path.write_text(pre_commit_content)
40
+ pre_commit_path.chmod(0o755)
41
+
42
+ commit_msg_content = (
43
+ "#!/bin/sh\n"
44
+ 'cd "$(git rev-parse --show-toplevel)"\n'
45
+ 'MESSAGE=$(cat "$1")\n'
46
+ 'poetry run python -c "import sys; import re; '
47
+ "pattern = r'^(feat|fix|docs|style|refactor|perf|test|chore)'; "
48
+ "msg = '''$MESSAGE'''; "
49
+ 'sys.exit(0 if re.match(pattern, msg) else 1)"\n'
50
+ )
51
+ commit_msg_path = self.hooks_dir / "commit-msg"
52
+ commit_msg_path.write_text(commit_msg_content)
53
+ commit_msg_path.chmod(0o755)
54
+
55
+ def _configure_git_hooks_path(self) -> None:
56
+ try:
57
+ subprocess.run(
58
+ ["git", "config", "core.hooksPath", ".hardlint/_"],
59
+ cwd=self.project_root,
60
+ check=True,
61
+ capture_output=True,
62
+ )
63
+ except subprocess.CalledProcessError as e:
64
+ raise RuntimeError(f"Failed to configure Git: {e.stderr.decode()}") from e
65
+
66
+ def _ensure_pyproject_config(self) -> None:
67
+ pyproject_content = self.pyproject_path.read_text()
68
+
69
+ if "[tool.ruff]" not in pyproject_content:
70
+ pyproject_content += "\n[tool.ruff]\n"
71
+ pyproject_content += "line-length = 100\n"
72
+ pyproject_content += 'target-version = "py310"\n'
73
+ pyproject_content += 'select = ["E", "F", "W", "I", "N", "C", "B"]\n'
74
+
75
+ if "[tool.black]" not in pyproject_content:
76
+ pyproject_content += "\n[tool.black]\n"
77
+ pyproject_content += "line-length = 100\n"
78
+
79
+ if "[tool.isort]" not in pyproject_content:
80
+ pyproject_content += "\n[tool.isort]\n"
81
+ pyproject_content += 'profile = "black"\n'
82
+ pyproject_content += "line_length = 100\n"
83
+
84
+ self.pyproject_path.write_text(pyproject_content)