CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-thefuck

Magnificent app which corrects your previous console command

Overview
Eval results
Files

rule-development.mddocs/

Rule Development

Creating custom correction rules, rule structure, and integration patterns. This guide enables extending the correction system with new command-specific logic to handle additional CLI tools and error patterns not covered by built-in rules.

Capabilities

Rule Function Requirements

Every rule module must implement these core functions for integration with the correction system.

def match(command, settings):
    """
    Determines if this rule applies to the given command.
    
    Parameters:
    - command (Command): The failed command with script, stdout, stderr
    - settings (Settings): Application settings and configuration
    
    Returns:
    bool: True if this rule can provide corrections for the command
    
    This function should analyze the command to determine applicability.
    Common patterns:
    - Check command.script for command name or arguments
    - Check command.stderr for specific error messages
    - Check command.stdout for output patterns
    - Use settings for rule configuration
    """

def get_new_command(command, settings):
    """
    Generates corrected command(s) for the matched command.
    
    Parameters:
    - command (Command): The failed command to correct
    - settings (Settings): Application settings and configuration
    
    Returns:
    str or list[str]: Single correction or list of possible corrections
    
    This function should return the corrected command string(s).
    Multiple corrections are ranked by order in the list.
    """

Optional Rule Attributes

Rules can define optional attributes to customize their behavior and integration.

enabled_by_default = True
    """
    bool: Whether this rule should be enabled by default.
    
    Default: True if not specified
    Set to False for experimental rules or rules that may conflict
    """

priority = 1000
    """
    int: Rule priority for ordering corrections.
    
    Lower numbers = higher priority (executed first)
    Default: conf.DEFAULT_PRIORITY (1000) if not specified
    
    Priority guidelines:
    - 100-500: Critical fixes (sudo, permission issues)
    - 500-1000: Common command corrections
    - 1000-1500: Typo fixes and suggestions
    - 1500+: Experimental or low-confidence corrections
    """

requires_output = True
    """
    bool: Whether this rule needs command output to function.
    
    Default: True if not specified
    Set to False for rules that only analyze command.script
    
    Rules with requires_output=False can work with:
    - Commands that produce no output
    - Timeouts or killed processes
    - Script-only analysis
    """

def side_effect(old_cmd, new_cmd, settings):
    """
    Optional function executed when the corrected command runs.
    
    Parameters:
    - old_cmd (Command): Original failed command
    - new_cmd (str): Corrected command being executed
    - settings (Settings): Application settings
    
    Returns:
    None
    
    Use for additional actions like:
    - Updating configuration files
    - Creating directories or files
    - Logging corrections
    - Cleaning up state
    """

Rule Development Patterns

Basic Command Name Matching

Simple rules that match based on command name or structure.

# Example: Fix 'gti' typo to 'git'
def match(command, settings):
    return command.script.startswith('gti ')

def get_new_command(command, settings):
    return command.script.replace('gti ', 'git ', 1)

enabled_by_default = True
priority = 900
requires_output = False

Error Message Pattern Matching

Rules that analyze error output to determine corrections.

# Example: Add sudo for permission denied errors
def match(command, settings):
    return ('permission denied' in command.stderr.lower() or
            'operation not permitted' in command.stderr.lower())

def get_new_command(command, settings):
    return f'sudo {command.script}'

enabled_by_default = True
priority = 100  # High priority for permission fixes

Complex Analysis with Multiple Corrections

Advanced rules that provide multiple correction options.

import re
from thefuck.utils import get_closest

def match(command, settings):
    return (command.script.startswith('git ') and
            'is not a git command' in command.stderr)

def get_new_command(command, settings):
    # Extract the invalid git command
    match = re.search(r"git: '(\w+)' is not a git command", command.stderr)
    if not match:
        return []
    
    invalid_cmd = match.group(1)
    git_commands = ['push', 'pull', 'commit', 'checkout', 'branch', 'merge', 'status']
    
    # Find similar commands
    closest = get_closest(invalid_cmd, git_commands, cutoff=0.6)
    if closest:
        base_cmd = command.script.replace(invalid_cmd, closest, 1)
        return [base_cmd]
    
    return []

enabled_by_default = True
priority = 1000

Application-Specific Rules with Decorators

Rules using utility decorators for cleaner code.

from thefuck.utils import for_app
from thefuck.specific.sudo import sudo_support

@sudo_support
@for_app('docker')
def match(command, settings):
    return 'permission denied' in command.stderr.lower()

@sudo_support
def get_new_command(command, settings):
    # sudo_support decorator automatically adds/removes sudo
    return command.script

enabled_by_default = True
priority = 200

Rules with Side Effects

Rules that perform additional actions when corrections are executed.

import os
from pathlib import Path

def match(command, settings):
    return (command.script.startswith('cd ') and
            'no such file or directory' in command.stderr.lower())

def get_new_command(command, settings):
    # Extract the directory name
    parts = command.script.split()
    if len(parts) > 1:
        directory = parts[1]
        return f'mkdir -p {directory} && cd {directory}'
    return command.script

def side_effect(old_cmd, new_cmd, settings):
    """Log directory creation for future reference."""
    parts = new_cmd.split('&&')[0].strip().split()
    if len(parts) > 2:
        created_dir = parts[2]
        log_file = Path.home() / '.thefuck' / 'created_dirs.log'
        with log_file.open('a') as f:
            f.write(f"{created_dir}\n")

enabled_by_default = True
priority = 1200

Advanced Rule Patterns

Multi-Application Rules

Rules that handle multiple related applications.

def match(command, settings):
    package_managers = ['apt-get', 'yum', 'dnf', 'pacman']
    return (any(command.script.startswith(pm) for pm in package_managers) and
            'permission denied' in command.stderr.lower())

def get_new_command(command, settings):
    return f'sudo {command.script}'

enabled_by_default = True
priority = 150

Context-Aware Rules

Rules that use environment context for better corrections.

import os
from thefuck.utils import which

def match(command, settings):
    return (command.script.startswith('python ') and
            'command not found' in command.stderr)

def get_new_command(command, settings):
    corrections = []
    
    # Try python3 if available
    if which('python3'):
        corrections.append(command.script.replace('python ', 'python3 ', 1))
    
    # Try py if on Windows
    if os.name == 'nt' and which('py'):
        corrections.append(command.script.replace('python ', 'py ', 1))
    
    return corrections

enabled_by_default = True
priority = 800

History-Based Rules

Rules that use command history for intelligent corrections.

from thefuck.utils import get_valid_history_without_current

def match(command, settings):
    return 'command not found' in command.stderr

def get_new_command(command, settings):
    history = get_valid_history_without_current(command)
    
    # Look for similar commands in history
    invalid_cmd = command.script.split()[0]
    for hist_cmd in history[:20]:  # Check recent history
        if hist_cmd.startswith(invalid_cmd[:-1]):  # Similar prefix
            return hist_cmd
    
    return []

enabled_by_default = False  # Experimental
priority = 1800

Rule Installation and Testing

User Rule Location

Custom rules should be placed in the user's rules directory:

~/.thefuck/rules/my_custom_rule.py

Rule Testing Template

# Template for testing custom rules
from thefuck.types import Command

def test_rule():
    """Test the custom rule functionality."""
    # Test command that should match
    test_command = Command(
        script="my_command --flag",
        stdout="",
        stderr="my_command: error message"
    )
    
    # Test match function
    assert match(test_command, {}) == True
    
    # Test correction generation
    correction = get_new_command(test_command, {})
    assert correction == "expected_correction"
    
    print("Rule tests passed!")

if __name__ == "__main__":
    test_rule()

Debugging Rules

from thefuck import logs

def match(command, settings):
    logs.debug(f"Testing rule against: {command.script}", settings)
    
    result = # ... rule logic ...
    
    logs.debug(f"Rule match result: {result}", settings)
    return result

def get_new_command(command, settings):
    logs.debug(f"Generating correction for: {command.script}", settings)
    
    correction = # ... correction logic ...
    
    logs.debug(f"Generated correction: {correction}", settings)
    return correction

Best Practices

Rule Design Guidelines

  1. Specificity: Make match conditions as specific as possible to avoid false positives
  2. Performance: Keep match functions fast - they're called frequently
  3. Robustness: Handle edge cases and malformed commands gracefully
  4. Testing: Test rules with various command variations and error messages
  5. Documentation: Include clear docstrings explaining rule purpose and behavior

Error Handling

def match(command, settings):
    try:
        # Rule logic that might fail
        return analyze_command(command)
    except Exception as e:
        # Log error but don't crash
        logs.exception(f"Rule match error: {e}", settings)
        return False

def get_new_command(command, settings):
    try:
        # Correction logic
        return generate_correction(command)
    except Exception as e:
        logs.exception(f"Rule correction error: {e}", settings)
        return []  # Return empty list if correction fails

Integration Patterns

# Use existing utilities for common operations
from thefuck.utils import replace_argument, get_closest, for_app
from thefuck.specific.sudo import sudo_support

# Combine decorators for powerful rule behavior
@sudo_support
@for_app('git')
def match(command, settings):
    return 'permission denied' in command.stderr

@sudo_support  # This will automatically handle sudo addition/removal
def get_new_command(command, settings):
    return command.script  # sudo_support handles the rest

This comprehensive rule development system allows users to extend thefuck's capabilities to handle any command-line tool or error pattern, making it a truly extensible correction system.

Install with Tessl CLI

npx tessl i tessl/pypi-thefuck

docs

configuration.md

core-application.md

data-types.md

index.md

rule-development.md

rule-system.md

shell-integration.md

user-interface.md

utilities.md

tile.json