0
# Rule System and Correction
1
2
Rule loading, command matching, and correction generation. This system processes failed commands through available rules to generate appropriate corrections using a sophisticated pattern-matching and correction-generation pipeline.
3
4
## Capabilities
5
6
### Rule Loading and Management
7
8
Functions for discovering, loading, and managing correction rules from both built-in and user-defined sources.
9
10
```python { .api }
11
def load_rule(rule, settings):
12
"""
13
Imports rule module and returns Rule object.
14
15
Parameters:
16
- rule (pathlib.Path): Path to rule Python file
17
- settings (Settings): Application settings
18
19
Returns:
20
Rule: Loaded rule object with all metadata
21
22
Loads rule module and extracts:
23
- match function
24
- get_new_command function
25
- enabled_by_default (default: True)
26
- side_effect (default: None)
27
- priority (default: DEFAULT_PRIORITY or custom from settings)
28
- requires_output (default: True)
29
"""
30
31
def get_loaded_rules(rules, settings):
32
"""
33
Yields all available rules that are enabled in settings.
34
35
Parameters:
36
- rules (iterable): Collection of rule file paths
37
- settings (Settings): Application settings containing enabled rules
38
39
Yields:
40
Rule: Each enabled and loaded rule
41
"""
42
43
def get_rules(user_dir, settings):
44
"""
45
Returns all enabled rules from built-in and user directories.
46
47
Parameters:
48
- user_dir (pathlib.Path): User configuration directory
49
- settings (Settings): Application settings
50
51
Returns:
52
list[Rule]: Sorted list of enabled rules by priority
53
54
Searches for rules in:
55
- Built-in rules directory (thefuck/rules/*.py)
56
- User rules directory (~/.thefuck/rules/*.py)
57
"""
58
```
59
60
### Command Matching and Processing
61
62
Functions for testing rule applicability and generating corrections from matched rules.
63
64
```python { .api }
65
def is_rule_match(command, rule, settings):
66
"""
67
Returns True if rule matches the given command.
68
69
Parameters:
70
- command (Command): Command to test against rule
71
- rule (Rule): Rule to test for match
72
- settings (Settings): Application settings
73
74
Returns:
75
bool: True if rule applies to the command
76
77
Handles:
78
- Script-only commands when rule requires output
79
- Exception handling for rule match functions
80
- Debug logging of rule matching attempts
81
"""
82
83
def make_corrected_commands(command, rule, settings):
84
"""
85
Generates corrected commands from a matched rule.
86
87
Parameters:
88
- command (Command): Original failed command
89
- rule (Rule): Matched rule to apply
90
- settings (Settings): Application settings
91
92
Yields:
93
CorrectedCommand: Each generated correction with proper priority
94
95
Handles:
96
- Single correction return values
97
- Multiple correction return values (lists)
98
- Priority calculation for multiple corrections
99
"""
100
101
def get_corrected_commands(command, user_dir, settings):
102
"""
103
Returns sorted sequence of corrected commands for a given command.
104
105
Parameters:
106
- command (Command): Original failed command
107
- user_dir (pathlib.Path): User configuration directory
108
- settings (Settings): Application settings
109
110
Returns:
111
SortedCorrectedCommandsSequence: Lazy-evaluated, sorted corrections
112
113
Process:
114
1. Load all enabled rules
115
2. Test each rule for matches
116
3. Generate corrections from matched rules
117
4. Return sorted, deduplicated sequence
118
"""
119
```
120
121
## Rule Structure
122
123
Every correction rule is a Python module that must implement specific functions and can define optional attributes:
124
125
### Required Functions
126
127
```python { .api }
128
def match(command, settings):
129
"""
130
Determines if this rule applies to the given command.
131
132
Parameters:
133
- command (Command): The failed command to analyze
134
- settings (Settings): Application settings
135
136
Returns:
137
bool: True if this rule can correct the command
138
139
This function should analyze command.script, command.stdout,
140
and command.stderr to determine applicability.
141
"""
142
143
def get_new_command(command, settings):
144
"""
145
Generates corrected command(s) for the matched command.
146
147
Parameters:
148
- command (Command): The failed command to correct
149
- settings (Settings): Application settings
150
151
Returns:
152
str or list[str]: Single correction or list of possible corrections
153
154
This function should return the corrected command string(s)
155
that should be executed instead of the original.
156
"""
157
```
158
159
### Optional Attributes
160
161
```python { .api }
162
enabled_by_default = True
163
"""
164
bool: Whether this rule is enabled by default.
165
Default: True if not specified.
166
"""
167
168
priority = 1000
169
"""
170
int: Rule priority for ordering corrections.
171
Lower numbers = higher priority.
172
Default: conf.DEFAULT_PRIORITY (1000) if not specified.
173
"""
174
175
requires_output = True
176
"""
177
bool: Whether this rule needs command output (stdout/stderr) to function.
178
Default: True if not specified.
179
Set to False for rules that only analyze command.script.
180
"""
181
182
def side_effect(old_cmd, new_cmd, settings):
183
"""
184
Optional function executed when the corrected command runs.
185
186
Parameters:
187
- old_cmd (Command): Original failed command
188
- new_cmd (str): Corrected command being executed
189
- settings (Settings): Application settings
190
191
Returns:
192
None
193
194
Used for additional actions like updating configuration,
195
cleaning up state, or logging corrections.
196
"""
197
```
198
199
## Built-in Rules Examples
200
201
The thefuck package includes 70+ built-in rules covering common command-line tools. Here are some examples:
202
203
### Git Push Rule Example
204
205
```python
206
# Example: thefuck/rules/git_push.py
207
import re
208
from thefuck.utils import for_app
209
210
@for_app('git')
211
def match(command, settings):
212
return (command.script.startswith('git push') and
213
'set-upstream' in command.stderr)
214
215
def get_new_command(command, settings):
216
branch = re.search(r'git push (\w+)', command.script)
217
if branch:
218
return f'git push --set-upstream origin {branch.group(1)}'
219
return 'git push --set-upstream origin main'
220
221
enabled_by_default = True
222
priority = 1000
223
```
224
225
### Sudo Rule Example
226
227
```python
228
# Example: thefuck/rules/sudo.py
229
def match(command, settings):
230
return ('permission denied' in command.stderr.lower() or
231
'operation not permitted' in command.stderr.lower())
232
233
def get_new_command(command, settings):
234
return f'sudo {command.script}'
235
236
enabled_by_default = True
237
priority = 100 # High priority
238
```
239
240
## Usage Examples
241
242
### Basic Rule Processing
243
244
```python
245
from thefuck.corrector import get_corrected_commands
246
from thefuck.types import Command
247
from thefuck.main import setup_user_dir
248
from thefuck.conf import get_settings
249
250
# Setup
251
user_dir = setup_user_dir()
252
settings = get_settings(user_dir)
253
254
# Create a failed command
255
command = Command(
256
script="git push origin main",
257
stdout="",
258
stderr="fatal: The current branch main has no upstream branch."
259
)
260
261
# Get corrections
262
corrections = get_corrected_commands(command, user_dir, settings)
263
264
# Access corrections
265
if corrections:
266
print(f"First correction: {corrections[0].script}")
267
print(f"Total corrections: {len(corrections)}")
268
269
for correction in corrections:
270
print(f" {correction.script} (priority: {correction.priority})")
271
```
272
273
### Manual Rule Testing
274
275
```python
276
from thefuck.corrector import load_rule, is_rule_match
277
from pathlib import Path
278
279
# Load a specific rule
280
rule_path = Path("thefuck/rules/git_push.py")
281
rule = load_rule(rule_path, settings)
282
283
# Test rule matching
284
command = Command("git push origin main", "", "set-upstream error...")
285
if is_rule_match(command, rule, settings):
286
print(f"Rule {rule.name} matches the command")
287
288
# Generate corrections
289
corrections = list(make_corrected_commands(command, rule, settings))
290
for correction in corrections:
291
print(f"Correction: {correction.script}")
292
```
293
294
### Custom Rule Loading
295
296
```python
297
from thefuck.corrector import get_rules
298
299
# Get all enabled rules
300
user_dir = setup_user_dir()
301
settings = get_settings(user_dir)
302
rules = get_rules(user_dir, settings)
303
304
print(f"Loaded {len(rules)} rules:")
305
for rule in rules[:5]: # Show first 5
306
print(f" {rule.name} (priority: {rule.priority})")
307
```
308
309
## Rule Development Patterns
310
311
### Command Analysis Patterns
312
313
```python
314
# Pattern: Check command name
315
def match(command, settings):
316
return command.script.startswith('docker')
317
318
# Pattern: Check error output
319
def match(command, settings):
320
return 'command not found' in command.stderr
321
322
# Pattern: Complex regex matching
323
import re
324
def match(command, settings):
325
return re.search(r'git \w+', command.script) and 'not a git command' in command.stderr
326
```
327
328
### Correction Generation Patterns
329
330
```python
331
# Pattern: Simple replacement
332
def get_new_command(command, settings):
333
return command.script.replace('pussh', 'push')
334
335
# Pattern: Multiple corrections
336
def get_new_command(command, settings):
337
return [
338
f'sudo {command.script}',
339
command.script.replace('apt-get', 'apt')
340
]
341
342
# Pattern: Context-aware correction
343
def get_new_command(command, settings):
344
if 'permission denied' in command.stderr:
345
return f'sudo {command.script}'
346
elif 'not found' in command.stderr:
347
return command.script.replace('ll', 'ls -la')
348
```
349
350
## Error Handling
351
352
The rule system handles various error conditions:
353
354
- **Rule loading errors**: Invalid rule modules are logged and skipped
355
- **Match function exceptions**: Caught and logged, rule is skipped for that command
356
- **Correction generation errors**: Caught and logged, rule produces no corrections
357
- **Priority conflicts**: Rules with same priority are ordered consistently
358
- **Circular dependencies**: Prevented through proper rule isolation