How Rules Are Loaded in TheFuck: A Deep Dive into the Rule Loading Pipeline
TheFuck loads rules through a four-step pipeline that discovers Python files from built-in, user, and third-party directories, imports them as Rule objects, and filters them based on user settings.
The open-source project nvbn/thefuck powers its command-line correction magic through a dynamic, extensible rule system. Understanding how rules are loaded in TheFuck reveals how the tool discovers corrections from its core library, your local configuration, and even third-party Python packages.
The Four-Step Rule Loading Pipeline
The entry point for rule discovery is thefuck.corrector, which orchestrates the loading process through three primary functions: get_rules_import_paths(), get_rules(), and get_loaded_rules().
Step 1: Discovering Rule Directories via get_rules_import_paths()
The pipeline begins by building a prioritized list of directories to scan. In thefuck/corrector.py (lines 22-38), the function get_rules_import_paths() yields paths from three distinct sources:
- Built-in rules: The core package at
thefuck/rules - User rules: The user-specific directory at
~/.config/thefuck/rules(falling back to the legacy~/.thefuck/rules) - Third-party rules: Any installed Python package named
thefuck_contrib_*found onsys.path
Step 2: Enumerating Python Files via get_rules()
Once directories are identified, get_rules() (lines 40-49 in thefuck/corrector.py) collects every Python file using Path.glob('*.py'). The function sorts these file paths alphabetically and returns the complete list to the loader, ensuring deterministic rule ordering.
Step 3: Importing and Instantiating Rules via get_loaded_rules()
The heavy lifting occurs in get_loaded_rules() (lines 8-20 in thefuck/corrector.py). For each file path discovered in Step 2, the system calls Rule.from_path(path).
Inside thefuck/types.py (lines 31-54), Rule.from_path uses a helper function load_source to dynamically import the Python module. It then extracts the required callables (match and get_new_command) along with optional metadata (priority, enabled_by_default, requires_output).
Only rules that pass the is_enabled check and are not explicitly excluded by the user are yielded as active Rule instances.
Step 4: Filtering by User Configuration via is_enabled()
The final gatekeeper is Rule.is_enabled in thefuck/types.py (lines 56-66). This method consults the global settings object (populated from thefuck/conf.py, environment variables, and CLI arguments).
A rule is considered enabled when:
- Its name appears in
settings.rules, or - It is marked
enabled_by_default=Trueand the special tokenALL_ENABLEDis present insettings.rules
Rules listed in settings.exclude_rules are ignored unconditionally, regardless of other settings.
Where Rules Come From: Built-in, User, and Third-Party Sources
TheFuck's architecture supports three distinct rule sources, allowing users to extend functionality without modifying core code.
Built-in Rules (thefuck/rules)
The core distribution includes dozens of rules in the thefuck/rules directory. These handle common command-line mistakes like git_push, git_commit, cd_parent, and sudo. These rules ship with the package and are updated with each release.
User Rules (~/.config/thefuck/rules)
Users can create custom rules by placing Python files in ~/.config/thefuck/rules (or the legacy ~/.thefuck/rules). The Settings._setup_user_dir() method in thefuck/conf.py (lines 68-75) ensures this directory exists and is included in the import path scan.
Third-Party Contributions (thefuck_contrib_*)
The system automatically discovers installed packages named with the thefuck_contrib_* prefix. In thefuck/corrector.py (lines 33-37), the code scans sys.path for these packages and includes their rules/ subdirectories in the loading pipeline, enabling community extensions distributed via PyPI.
Practical Examples: Working with Rules
Listing All Loaded Rules
You can inspect which rules are currently active in your Python environment:
from thefuck.corrector import get_rules
# List names of all enabled rules
rule_names = [rule.name for rule in get_rules()]
print(rule_names)
# Output: ['git_push', 'git_commit', 'cd_parent', 'sudo', ...]
Creating a Custom Rule
Create a file at ~/.config/thefuck/rules/my_echo.py:
# my_echo.py
priority = 100 # Optional: defaults to thefuck.const.DEFAULT_PRIORITY
def match(command):
"""Detect when user types 'ech' instead of 'echo'."""
return command.script.startswith('ech ')
def get_new_command(command):
"""Correct 'ech ' to 'echo '."""
return command.script.replace('ech ', 'echo ', 1)
After saving, the rule automatically appears in the next thefuck invocation because get_rules_import_paths() includes your user configuration directory.
Disabling Specific Rules
Use the environment variable to exclude built-in rules:
export THEFUCK_EXCLUDE_RULES=git_push,git_commit
thefuck
This is processed in thefuck/conf.py via Settings._settings_from_env() and Settings._val_from_env(), then checked in Rule.is_enabled().
Enabling All Rules Explicitly
To force-load every available rule (including those disabled by default):
export THEFUCK_RULES=ALL_ENABLED
thefuck
The ALL_ENABLED token is defined in thefuck/const.py and evaluated in Rule.is_enabled() within thefuck/types.py.
Summary
- TheFuck loads rules through a four-step pipeline defined in
thefuck/corrector.py: directory discovery, file enumeration, dynamic import, and user-configurable filtering. - Rules originate from three sources: the built-in
thefuck/rulespackage, user-specific files in~/.config/thefuck/rules, and third-party packages prefixed withthefuck_contrib_*. - Activation is controlled by the
Rule.is_enabled()method inthefuck/types.py, which checks againstsettings.rules,settings.exclude_rules, and theALL_ENABLEDsentinel. - Custom rules require only two functions:
match(command)andget_new_command(command), placed in a.pyfile in the user rules directory.
Frequently Asked Questions
How does TheFuck find custom rules I create?
TheFuck discovers custom rules by scanning ~/.config/thefuck/rules (or the legacy ~/.thefuck/rules) during the get_rules_import_paths() execution in thefuck/corrector.py. Any Python file placed in this directory is automatically imported and evaluated for the required match and get_new_command functions.
Can I disable built-in rules without modifying the source code?
Yes, you can disable specific rules using the THEFUCK_EXCLUDE_RULES environment variable or the exclude_rules setting in your configuration. The Rule.is_enabled() method in thefuck/types.py checks this exclusion list before activating any rule, allowing you to suppress unwanted corrections like git_push or sudo without touching the core package files.
What is the ALL_ENABLED setting and when should I use it?
ALL_ENABLED is a special sentinel value defined in thefuck/const.py that forces the loading of every available rule, including those normally disabled by default. When you set THEFUCK_RULES=ALL_ENABLED or include ALL_ENABLED in your rules list, the Rule.is_enabled() logic bypasses the default enablement checks. Use this when you want to experiment with experimental or disabled community rules.
How do third-party thefuck_contrib_* packages work?
Third-party rule packages follow a naming convention: any installed Python package prefixed with thefuck_contrib_ is automatically discovered by get_rules_import_paths() in thefuck/corrector.py (lines 33-37). The loader searches sys.path for these packages and includes their rules/ subdirectories in the scanning process, allowing community contributions to be distributed via PyPI and integrated without manual configuration.
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 →