How the Rule Matching Process Works in TheFuck: From Failed Command to Fix
TheFuck's rule matching pipeline executes four sequential stages: wrapping the failed terminal command in a Command object, dynamically loading available rule modules from bundled and user directories, filtering for enabled rules via global settings, and testing each rule's match function to generate prioritized corrections.
The rule matching process is the core algorithm powering nvbn/thefuck, the popular command-line tool that suggests corrections for mistyped or failed terminal commands. Implemented across thefuck/types.py, thefuck/corrector.py, and thefuck/conf.py, this engine evaluates Python-based rules against the failed command context to produce executable fixes.
Stage 1: Wrapping the Failed Command in a Command Object
The pipeline initiates in thefuck/types.py where Command.from_raw_script (lines 71‑82) instantiates a Command object. This class encapsulates the raw script array, captured stdout/stderr output, and helper properties like script_parts which tokenizes the command for downstream analysis. The Command instance serves as the immutable input context for all subsequent rule evaluations.
Stage 2: Discovering and Loading Rule Modules
Rule discovery occurs in thefuck/corrector.py through two key functions. First, get_rules_import_paths (lines 22‑38) yields filesystem paths for:
- The bundled
rulesdirectory distributed with the package - The user's custom rules directory in their configuration path
- Any installed third-party packages prefixed with
thefuck_contrib_
Next, get_loaded_rules (lines 8‑19) iterates these paths and invokes Rule.from_path (lines 31‑53 in thefuck/types.py) for each Python file. This method uses load_source to dynamically import the module, then extracts the rule's match function, get_new_command generator, priority value, and requires_output flag to construct a typed Rule object.
Stage 3: Filtering Enabled Rules
Before pattern matching executes, Rule.is_enabled (lines 55‑66 in thefuck/types.py) applies configuration filters. The method consults the global settings object loaded from thefuck/conf.py, checking whether the rule name appears in settings.rules or if the ALL_ENABLED flag from thefuck/const.py is active. Users customize this behavior via the THEFUCK_RULES environment variable or the exclude_rules configuration option.
Stage 4: Executing Match Logic and Generating Corrections
For each enabled rule, the engine invokes Rule.is_match (lines 168‑178 in thefuck/types.py), which implements a defensive three-step validation:
- Output requirement check: If the rule specifies
requires_output = Truebutcommand.outputisNone, the function returnsFalseimmediately - Pattern matching: The rule's custom
match(command)function executes inside atry/exceptblock to catch runtime errors - Command generation: Upon a
Trueresult,Rule.get_corrected_commands(lines 185‑199) calls the rule'sget_new_commandgenerator and wraps each suggestion in aCorrectedCommandobject with calculated priority
For example, thefuck/rules/git_commit_add.py (lines 5‑10) implements match by verifying that "commit" exists in command.script_parts and that the error text contains the substring "no changes added to commit".
Aggregating and Prioritizing Suggestions
The get_corrected_commands function in thefuck/corrector.py (lines 81‑92) aggregates all matched rules into a generator yielding CorrectedCommand instances. Subsequently, organize_commands (lines 52‑78) deduplicates identical suggestions and sorts them by the rule's priority attribute. The final ordered list surfaces the most likely fix first when the user executes the fuck command.
Programmatic Rule Matching Example
You can interact directly with the rule matching process using the internal API:
from thefuck.types import Command
from thefuck.corrector import get_corrected_commands
# Simulate a failed git commit without staged changes
raw_script = ['git', 'commit']
output = ('On branch master\n'
'nothing to commit, working tree clean\n'
'no changes added to commit (use "git add" and/or "git commit -a")')
cmd = Command.from_raw_script(raw_script)
# Retrieve all corrected commands suggested by enabled rules
for corrected in get_corrected_commands(cmd):
print('Suggested fix:', corrected.script)
Executing this script outputs suggestions such as git commit -a and git commit -p, generated when the git_commit_add rule's match function returns True based on the simulated error context.
Summary
- Command encapsulation:
Command.from_raw_scriptinthefuck/types.py(lines 71‑82) creates an object containing the script and terminal output. - Dynamic discovery:
get_rules_import_pathsinthefuck/corrector.py(lines 22‑38) locates rules in bundled, user, and third-party directories. - Configuration filtering:
Rule.is_enabledcheckssettings.rulesfromthefuck/conf.pyand theALL_ENABLEDflag fromthefuck/const.py. - Defensive matching:
Rule.is_matchvalidatesrequires_outputand executes the rule'smatchfunction inside exception handling. - Result optimization:
organize_commandsinthefuck/corrector.py(lines 52‑78) sorts deduplicated suggestions by priority before display.
Frequently Asked Questions
How does TheFuck determine which rules are enabled?
The Rule.is_enabled method in thefuck/types.py (lines 55‑66) evaluates the global settings.rules dictionary managed by thefuck/conf.py. Rules are active if explicitly included in the configuration or if the ALL_ENABLED constant from thefuck/const.py is set. Users override this via the THEFUCK_RULES environment variable or the exclude_rules config option.
What happens when multiple rules match the same command?
When multiple rules return True from their match functions, get_corrected_commands in thefuck/corrector.py (lines 81‑92) collects all suggestions. The organize_commands function (lines 52‑78) then removes duplicates and sorts results by the rule's priority attribute, ensuring higher-priority corrections appear first.
Can individual rules require command output to function?
Yes, rules can define requires_output = True in their module. During matching, Rule.is_match (lines 168‑178 in thefuck/types.py) first checks if command.output is None. If a rule requires output but the command produced none, the matching logic short-circuits to False without executing the rule's match function.
Where are rule modules stored and how are they loaded?
Rule modules reside in three locations: the bundled thefuck/rules directory, the user's custom rules directory, and any installed thefuck_contrib_* packages. The get_rules_import_paths function in thefuck/corrector.py (lines 22‑38) discovers these paths, while Rule.from_path (lines 31‑53 in thefuck/types.py) dynamically imports each Python file using load_source and extracts the rule's callables and metadata.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →