File: release.py

#!/usr/bin/env python
from subprocess import call
import os
import re


version = None


def get_new_setup_py_lines():
    global version
    with open('setup.py', 'r') as sf:
        current_setup = sf.readlines()
    for line in current_setup:
        if line.startswith('VERSION = '):
            major, minor = re.findall(r"VERSION = '(\d+)\.(\d+)'", line)[0]
            version = "{}.{}".format(major, int(minor) + 1)
            yield "VERSION = '{}'\n".format(version)
        else:
            yield line


lines = list(get_new_setup_py_lines())
with open('setup.py', 'w') as sf:
    sf.writelines(lines)

call('git pull', shell=True)
call('git commit -am "Bump to {}"'.format(version), shell=True)
call('git tag {}'.format(version), shell=True)
call('git push', shell=True)
call('git push --tags', shell=True)

env = os.environ
env['CONVERT_README'] = 'true'
call('rm -rf dist/*', shell=True, env=env)
call('python setup.py sdist bdist_wheel', shell=True, env=env)
call('twine upload dist/*', shell=True, env=env)



File: fastentrypoints.py

# Copyright (c) 2016, Aaron Christianson
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# 1. Redistributions of source code must retain the above copyright
#    notice, this list of conditions and the following disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright
#    notice, this list of conditions and the following disclaimer in the
#    documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
# PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
# TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
'''
Monkey patch setuptools to write faster console_scripts with this format:

    import sys
    from mymodule import entry_function
    sys.exit(entry_function())

This is better.

(c) 2016, Aaron Christianson
http://github.com/ninjaaron/fast-entry_points
'''
from setuptools.command import easy_install
import re
TEMPLATE = r'''\
# -*- coding: utf-8 -*-
# EASY-INSTALL-ENTRY-SCRIPT: '{3}','{4}','{5}'
__requires__ = '{3}'
import re
import sys

from {0} import {1}

if __name__ == '__main__':
    sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0])
    sys.exit({2}())'''


@classmethod
def get_args(cls, dist, header=None):
    """
    Yield write_script() argument tuples for a distribution's
    console_scripts and gui_scripts entry points.
    """
    if header is None:
        header = cls.get_header()
    spec = str(dist.as_requirement())
    for type_ in 'console', 'gui':
        group = type_ + '_scripts'
        for name, ep in dist.get_entry_map(group).items():
            # ensure_safe_name
            if re.search(r'[\\/]', name):
                raise ValueError("Path separators not allowed in script names")
            script_text = TEMPLATE.format(
                          ep.module_name, ep.attrs[0], '.'.join(ep.attrs),
                          spec, group, name)
            args = cls._get_script_args(type_, name, header, script_text)
            for res in args:
                yield res


easy_install.ScriptWriter.get_args = get_args


def main():
    import os
    import re
    import shutil
    import sys
    dests = sys.argv[1:] or ['.']
    filename = re.sub(r'\.pyc$', '.py', __file__)

    for dst in dests:
        shutil.copy(filename, dst)
        manifest_path = os.path.join(dst, 'MANIFEST.in')
        setup_path = os.path.join(dst, 'setup.py')

        # Insert the include statement to MANIFEST.in if not present
        with open(manifest_path, 'a+') as manifest:
            manifest.seek(0)
            manifest_content = manifest.read()
            if not 'include fastentrypoints.py' in manifest_content:
                manifest.write(('\n' if manifest_content else '')
                               + 'include fastentrypoints.py')

        # Insert the import statement to setup.py if not present
        with open(setup_path, 'a+') as setup:
            setup.seek(0)
            setup_content = setup.read()
            if not 'import fastentrypoints' in setup_content:
                setup.seek(0)
                setup.truncate()
                setup.write('import fastentrypoints\n' + setup_content)

print(__name__)



File: setup.py

#!/usr/bin/env python
from setuptools import setup, find_packages
import pkg_resources
import sys
import os
import fastentrypoints


try:
    if int(pkg_resources.get_distribution("pip").version.split('.')[0]) < 6:
        print('pip older than 6.0 not supported, please upgrade pip with:\n\n'
              '    pip install -U pip')
        sys.exit(-1)
except pkg_resources.DistributionNotFound:
    pass

if os.environ.get('CONVERT_README'):
    import pypandoc

    long_description = pypandoc.convert('README.md', 'rst')
else:
    long_description = ''

version = sys.version_info[:2]
if version < (2, 7):
    print('thefuck requires Python version 2.7 or later' +
          ' ({}.{} detected).'.format(*version))
    sys.exit(-1)
elif (3, 0) < version < (3, 5):
    print('thefuck requires Python version 3.5 or later' +
          ' ({}.{} detected).'.format(*version))
    sys.exit(-1)

VERSION = '3.32'

install_requires = ['psutil', 'colorama', 'six']
extras_require = {':python_version<"3.4"': ['pathlib2'],
                  ':python_version<"3.3"': ['backports.shutil_get_terminal_size'],
                  ':python_version<="2.7"': ['decorator<5', 'pyte<0.8.1'],
                  ':python_version>"2.7"': ['decorator', 'pyte'],
                  ":sys_platform=='win32'": ['win_unicode_console']}

if sys.platform == "win32":
    scripts = ['scripts\\fuck.bat', 'scripts\\fuck.ps1']
    entry_points = {'console_scripts': [
                  'thefuck = thefuck.entrypoints.main:main',
                  'thefuck_firstuse = thefuck.entrypoints.not_configured:main']}
else:
    scripts = []
    entry_points = {'console_scripts': [
                  'thefuck = thefuck.entrypoints.main:main',
                  'fuck = thefuck.entrypoints.not_configured:main']}

setup(name='thefuck',
      version=VERSION,
      description="Magnificent app which corrects your previous console command",
      long_description=long_description,
      author='Vladimir Iakovlev',
      author_email='nvbn.rm@gmail.com',
      url='https://github.com/nvbn/thefuck',
      license='MIT',
      packages=find_packages(exclude=['ez_setup', 'examples',
                                      'tests', 'tests.*', 'release']),
      include_package_data=True,
      zip_safe=False,
      python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*',
      install_requires=install_requires,
      extras_require=extras_require,
      scripts=scripts,
      entry_points=entry_points)



File: thefuck/corrector.py

import sys
from .conf import settings
from .types import Rule
from .system import Path
from . import logs


def get_loaded_rules(rules_paths):
    """Yields all available rules.

    :type rules_paths: [Path]
    :rtype: Iterable[Rule]

    """
    for path in rules_paths:
        if path.name != '__init__.py':
            rule = Rule.from_path(path)
            if rule and rule.is_enabled:
                yield rule


def get_rules_import_paths():
    """Yields all rules import paths.

    :rtype: Iterable[Path]

    """
    # Bundled rules:
    yield Path(__file__).parent.joinpath('rules')
    # Rules defined by user:
    yield settings.user_dir.joinpath('rules')
    # Packages with third-party rules:
    for path in sys.path:
        for contrib_module in Path(path).glob('thefuck_contrib_*'):
            contrib_rules = contrib_module.joinpath('rules')
            if contrib_rules.is_dir():
                yield contrib_rules


def get_rules():
    """Returns all enabled rules.

    :rtype: [Rule]

    """
    paths = [rule_path for path in get_rules_import_paths()
             for rule_path in sorted(path.glob('*.py'))]
    return sorted(get_loaded_rules(paths),
                  key=lambda rule: rule.priority)


def organize_commands(corrected_commands):
    """Yields sorted commands without duplicates.

    :type corrected_commands: Iterable[thefuck.types.CorrectedCommand]
    :rtype: Iterable[thefuck.types.CorrectedCommand]

    """
    try:
        first_command = next(corrected_commands)
        yield first_command
    except StopIteration:
        return

    without_duplicates = {
        command for command in sorted(
            corrected_commands, key=lambda command: command.priority)
        if command != first_command}

    sorted_commands = sorted(
        without_duplicates,
        key=lambda corrected_command: corrected_command.priority)

    logs.debug(u'Corrected commands: {}'.format(
        ', '.join(u'{}'.format(cmd) for cmd in [first_command] + sorted_commands)))

    for command in sorted_commands:
        yield command


def get_corrected_commands(command):
    """Returns generator with sorted and unique corrected commands.

    :type command: thefuck.types.Command
    :rtype: Iterable[thefuck.types.CorrectedCommand]

    """
    corrected_commands = (
        corrected for rule in get_rules()
        if rule.is_match(command)
        for corrected in rule.get_corrected_commands(command))
    return organize_commands(corrected_commands)



File: thefuck/ui.py

# -*- encoding: utf-8 -*-

import sys
from .conf import settings
from .exceptions import NoRuleMatched
from .system import get_key
from .utils import get_alias
from . import logs, const


def read_actions():
    """Yields actions for pressed keys."""
    while True:
        key = get_key()

        # Handle arrows, j/k (qwerty), and n/e (colemak)
        if key in (const.KEY_UP, const.KEY_CTRL_N, 'k', 'e'):
            yield const.ACTION_PREVIOUS
        elif key in (const.KEY_DOWN, const.KEY_CTRL_P, 'j', 'n'):
            yield const.ACTION_NEXT
        elif key in (const.KEY_CTRL_C, 'q'):
            yield const.ACTION_ABORT
        elif key in ('\n', '\r'):
            yield const.ACTION_SELECT


class CommandSelector(object):
    """Helper for selecting rule from rules list."""

    def __init__(self, commands):
        """:type commands: Iterable[thefuck.types.CorrectedCommand]"""
        self._commands_gen = commands
        try:
            self._commands = [next(self._commands_gen)]
        except StopIteration:
            raise NoRuleMatched
        self._realised = False
        self._index = 0

    def _realise(self):
        if not self._realised:
            self._commands += list(self._commands_gen)
            self._realised = True

    def next(self):
        self._realise()
        self._index = (self._index + 1) % len(self._commands)

    def previous(self):
        self._realise()
        self._index = (self._index - 1) % len(self._commands)

    @property
    def value(self):
        """:rtype thefuck.types.CorrectedCommand"""
        return self._commands[self._index]


def select_command(corrected_commands):
    """Returns:

     - the first command when confirmation disabled;
     - None when ctrl+c pressed;
     - selected command.

    :type corrected_commands: Iterable[thefuck.types.CorrectedCommand]
    :rtype: thefuck.types.CorrectedCommand | None

    """
    try:
        selector = CommandSelector(corrected_commands)
    except NoRuleMatched:
        logs.failed('No fucks given' if get_alias() == 'fuck'
                    else 'Nothing found')
        return

    if not settings.require_confirmation:
        logs.show_corrected_command(selector.value)
        return selector.value

    logs.confirm_text(selector.value)

    for action in read_actions():
        if action == const.ACTION_SELECT:
            sys.stderr.write('\n')
            return selector.value
        elif action == const.ACTION_ABORT:
            logs.failed('\nAborted')
            return
        elif action == const.ACTION_PREVIOUS:
            selector.previous()
            logs.confirm_text(selector.value)
        elif action == const.ACTION_NEXT:
            selector.next()
            logs.confirm_text(selector.value)



File: thefuck/conf.py

import os
import sys
from warnings import warn
from six import text_type
from . import const
from .system import Path

try:
    import importlib.util

    def load_source(name, pathname, _file=None):
        module_spec = importlib.util.spec_from_file_location(name, pathname)
        module = importlib.util.module_from_spec(module_spec)
        module_spec.loader.exec_module(module)
        return module
except ImportError:
    from imp import load_source


class Settings(dict):
    def __getattr__(self, item):
        return self.get(item)

    def __setattr__(self, key, value):
        self[key] = value

    def init(self, args=None):
        """Fills `settings` with values from `settings.py` and env."""
        from .logs import exception

        self._setup_user_dir()
        self._init_settings_file()

        try:
            self.update(self._settings_from_file())
        except Exception:
            exception("Can't load settings from file", sys.exc_info())

        try:
            self.update(self._settings_from_env())
        except Exception:
            exception("Can't load settings from env", sys.exc_info())

        self.update(self._settings_from_args(args))

    def _init_settings_file(self):
        settings_path = self.user_dir.joinpath('settings.py')
        if not settings_path.is_file():
            with settings_path.open(mode='w') as settings_file:
                settings_file.write(const.SETTINGS_HEADER)
                for setting in const.DEFAULT_SETTINGS.items():
                    settings_file.write(u'# {} = {}\n'.format(*setting))

    def _get_user_dir_path(self):
        """Returns Path object representing the user config resource"""
        xdg_config_home = os.environ.get('XDG_CONFIG_HOME', '~/.config')
        user_dir = Path(xdg_config_home, 'thefuck').expanduser()
        legacy_user_dir = Path('~', '.thefuck').expanduser()

        # For backward compatibility use legacy '~/.thefuck' if it exists:
        if legacy_user_dir.is_dir():
            warn(u'Config path {} is deprecated. Please move to {}'.format(
                legacy_user_dir, user_dir))
            return legacy_user_dir
        else:
            return user_dir

    def _setup_user_dir(self):
        """Returns user config dir, create it when it doesn't exist."""
        user_dir = self._get_user_dir_path()

        rules_dir = user_dir.joinpath('rules')
        if not rules_dir.is_dir():
            rules_dir.mkdir(parents=True)
        self.user_dir = user_dir

    def _settings_from_file(self):
        """Loads settings from file."""
        settings = load_source(
            'settings', text_type(self.user_dir.joinpath('settings.py')))
        return {key: getattr(settings, key)
                for key in const.DEFAULT_SETTINGS.keys()
                if hasattr(settings, key)}

    def _rules_from_env(self, val):
        """Transforms rules list from env-string to python."""
        val = val.split(':')
        if 'DEFAULT_RULES' in val:
            val = const.DEFAULT_RULES + [rule for rule in val if rule != 'DEFAULT_RULES']
        return val

    def _priority_from_env(self, val):
        """Gets priority pairs from env."""
        for part in val.split(':'):
            try:
                rule, priority = part.split('=')
                yield rule, int(priority)
            except ValueError:
                continue

    def _val_from_env(self, env, attr):
        """Transforms env-strings to python."""
        val = os.environ[env]
        if attr in ('rules', 'exclude_rules'):
            return self._rules_from_env(val)
        elif attr == 'priority':
            return dict(self._priority_from_env(val))
        elif attr in ('wait_command', 'history_limit', 'wait_slow_command',
                      'num_close_matches'):
            return int(val)
        elif attr in ('require_confirmation', 'no_colors', 'debug',
                      'alter_history', 'instant_mode'):
            return val.lower() == 'true'
        elif attr in ('slow_commands', 'excluded_search_path_prefixes'):
            return val.split(':')
        else:
            return val

    def _settings_from_env(self):
        """Loads settings from env."""
        return {attr: self._val_from_env(env, attr)
                for env, attr in const.ENV_TO_ATTR.items()
                if env in os.environ}

    def _settings_from_args(self, args):
        """Loads settings from args."""
        if not args:
            return {}

        from_args = {}
        if args.yes:
            from_args['require_confirmation'] = not args.yes
        if args.debug:
            from_args['debug'] = args.debug
        if args.repeat:
            from_args['repeat'] = args.repeat
        return from_args


settings = Settings(const.DEFAULT_SETTINGS)



File: thefuck/__init__.py




File: thefuck/types.py

import os
import sys
from . import logs
from .shells import shell
from .conf import settings, load_source
from .const import DEFAULT_PRIORITY, ALL_ENABLED
from .exceptions import EmptyCommand
from .utils import get_alias, format_raw_script
from .output_readers import get_output


class Command(object):
    """Command that should be fixed."""

    def __init__(self, script, output):
        """Initializes command with given values.

        :type script: basestring
        :type output: basestring

        """
        self.script = script
        self.output = output

    @property
    def stdout(self):
        logs.warn('`stdout` is deprecated, please use `output` instead')
        return self.output

    @property
    def stderr(self):
        logs.warn('`stderr` is deprecated, please use `output` instead')
        return self.output

    @property
    def script_parts(self):
        if not hasattr(self, '_script_parts'):
            try:
                self._script_parts = shell.split_command(self.script)
            except Exception:
                logs.debug(u"Can't split command script {} because:\n {}".format(
                    self, sys.exc_info()))
                self._script_parts = []

        return self._script_parts

    def __eq__(self, other):
        if isinstance(other, Command):
            return (self.script, self.output) == (other.script, other.output)
        else:
            return False

    def __repr__(self):
        return u'Command(script={}, output={})'.format(
            self.script, self.output)

    def update(self, **kwargs):
        """Returns new command with replaced fields.

        :rtype: Command

        """
        kwargs.setdefault('script', self.script)
        kwargs.setdefault('output', self.output)
        return Command(**kwargs)

    @classmethod
    def from_raw_script(cls, raw_script):
        """Creates instance of `Command` from a list of script parts.

        :type raw_script: [basestring]
        :rtype: Command
        :raises: EmptyCommand

        """
        script = format_raw_script(raw_script)
        if not script:
            raise EmptyCommand

        expanded = shell.from_shell(script)
        output = get_output(script, expanded)
        return cls(expanded, output)


class Rule(object):
    """Rule for fixing commands."""

    def __init__(self, name, match, get_new_command,
                 enabled_by_default, side_effect,
                 priority, requires_output):
        """Initializes rule with given fields.

        :type name: basestring
        :type match: (Command) -> bool
        :type get_new_command: (Command) -> (basestring | [basestring])
        :type enabled_by_default: boolean
        :type side_effect: (Command, basestring) -> None
        :type priority: int
        :type requires_output: bool

        """
        self.name = name
        self.match = match
        self.get_new_command = get_new_command
        self.enabled_by_default = enabled_by_default
        self.side_effect = side_effect
        self.priority = priority
        self.requires_output = requires_output

    def __eq__(self, other):
        if isinstance(other, Rule):
            return ((self.name, self.match, self.get_new_command,
                     self.enabled_by_default, self.side_effect,
                     self.priority, self.requires_output)
                    == (other.name, other.match, other.get_new_command,
                        other.enabled_by_default, other.side_effect,
                        other.priority, other.requires_output))
        else:
            return False

    def __repr__(self):
        return 'Rule(name={}, match={}, get_new_command={}, ' \
               'enabled_by_default={}, side_effect={}, ' \
               'priority={}, requires_output={})'.format(
                   self.name, self.match, self.get_new_command,
                   self.enabled_by_default, self.side_effect,
                   self.priority, self.requires_output)

    @classmethod
    def from_path(cls, path):
        """Creates rule instance from path.

        :type path: pathlib.Path
        :rtype: Rule

        """
        name = path.name[:-3]
        if name in settings.exclude_rules:
            logs.debug(u'Ignoring excluded rule: {}'.format(name))
            return
        with logs.debug_time(u'Importing rule: {};'.format(name)):
            try:
                rule_module = load_source(name, str(path))
            except Exception:
                logs.exception(u"Rule {} failed to load".format(name), sys.exc_info())
                return
        priority = getattr(rule_module, 'priority', DEFAULT_PRIORITY)
        return cls(name, rule_module.match,
                   rule_module.get_new_command,
                   getattr(rule_module, 'enabled_by_default', True),
                   getattr(rule_module, 'side_effect', None),
                   settings.priority.get(name, priority),
                   getattr(rule_module, 'requires_output', True))

    @property
    def is_enabled(self):
        """Returns `True` when rule enabled.

        :rtype: bool

        """
        return (
            self.name in settings.rules
            or self.enabled_by_default
            and ALL_ENABLED in settings.rules
        )

    def is_match(self, command):
        """Returns `True` if rule matches the command.

        :type command: Command
        :rtype: bool

        """
        if command.output is None and self.requires_output:
            return False

        try:
            with logs.debug_time(u'Trying rule: {};'.format(self.name)):
                if self.match(command):
                    return True
        except Exception:
            logs.rule_failed(self, sys.exc_info())

    def get_corrected_commands(self, command):
        """Returns generator with corrected commands.

        :type command: Command
        :rtype: Iterable[CorrectedCommand]

        """
        new_commands = self.get_new_command(command)
        if not isinstance(new_commands, list):
            new_commands = (new_commands,)
        for n, new_command in enumerate(new_commands):
            yield CorrectedCommand(script=new_command,
                                   side_effect=self.side_effect,
                                   priority=(n + 1) * self.priority)


class CorrectedCommand(object):
    """Corrected by rule command."""

    def __init__(self, script, side_effect, priority):
        """Initializes instance with given fields.

        :type script: basestring
        :type side_effect: (Command, basestring) -> None
        :type priority: int

        """
        self.script = script
        self.side_effect = side_effect
        self.priority = priority

    def __eq__(self, other):
        """Ignores `priority` field."""
        if isinstance(other, CorrectedCommand):
            return (other.script, other.side_effect) == \
                   (self.script, self.side_effect)
        else:
            return False

    def __hash__(self):
        return (self.script, self.side_effect).__hash__()

    def __repr__(self):
        return u'CorrectedCommand(script={}, side_effect={}, priority={})'.format(
            self.script, self.side_effect, self.priority)

    def _get_script(self):
        """Returns fixed commands script.

        If `settings.repeat` is `True`, appends command with second attempt
        of running fuck in case fixed command fails again.

        """
        if settings.repeat:
            repeat_fuck = '{} --repeat {}--force-command {}'.format(
                get_alias(),
                '--debug ' if settings.debug else '',
                shell.quote(self.script))
            return shell.or_(self.script, repeat_fuck)
        else:
            return self.script

    def run(self, old_cmd):
        """Runs command from rule for passed command.

        :type old_cmd: Command

        """
        if self.side_effect:
            self.side_effect(old_cmd, self.script)
        if settings.alter_history:
            shell.put_to_history(self.script)
        # This depends on correct setting of PYTHONIOENCODING by the alias:
        logs.debug(u'PYTHONIOENCODING: {}'.format(
            os.environ.get('PYTHONIOENCODING', '!!not-set!!')))

        sys.stdout.write(self._get_script())



File: thefuck/utils.py

import atexit
import os
import pickle
import re
import shelve
import sys
import six
from decorator import decorator
from difflib import get_close_matches as difflib_get_close_matches
from functools import wraps
from .logs import warn, exception
from .conf import settings
from .system import Path

DEVNULL = open(os.devnull, 'w')

if six.PY2:
    import anydbm
    shelve_open_error = anydbm.error
else:
    import dbm
    shelve_open_error = dbm.error


def memoize(fn):
    """Caches previous calls to the function."""
    memo = {}

    @wraps(fn)
    def wrapper(*args, **kwargs):
        if not memoize.disabled:
            key = pickle.dumps((args, kwargs))
            if key not in memo:
                memo[key] = fn(*args, **kwargs)
            value = memo[key]
        else:
            # Memoize is disabled, call the function
            value = fn(*args, **kwargs)

        return value

    return wrapper


memoize.disabled = False


@memoize
def which(program):
    """Returns `program` path or `None`."""
    try:
        from shutil import which

        return which(program)
    except ImportError:
        def is_exe(fpath):
            return os.path.isfile(fpath) and os.access(fpath, os.X_OK)

        fpath, fname = os.path.split(program)
        if fpath:
            if is_exe(program):
                return program
        else:
            for path in os.environ["PATH"].split(os.pathsep):
                path = path.strip('"')
                exe_file = os.path.join(path, program)
                if is_exe(exe_file):
                    return exe_file

        return None


def default_settings(params):
    """Adds default values to settings if it not presented.

    Usage:

        @default_settings({'apt': '/usr/bin/apt'})
        def match(command):
            print(settings.apt)

    """
    def _default_settings(fn, command):
        for k, w in params.items():
            settings.setdefault(k, w)
        return fn(command)
    return decorator(_default_settings)


def get_closest(word, possibilities, cutoff=0.6, fallback_to_first=True):
    """Returns closest match or just first from possibilities."""
    possibilities = list(possibilities)
    try:
        return difflib_get_close_matches(word, possibilities, 1, cutoff)[0]
    except IndexError:
        if fallback_to_first:
            return possibilities[0]


def get_close_matches(word, possibilities, n=None, cutoff=0.6):
    """Overrides `difflib.get_close_match` to control argument `n`."""
    if n is None:
        n = settings.num_close_matches
    return difflib_get_close_matches(word, possibilities, n, cutoff)


def include_path_in_search(path):
    return not any(path.startswith(x) for x in settings.excluded_search_path_prefixes)


@memoize
def get_all_executables():
    from thefuck.shells import shell

    def _safe(fn, fallback):
        try:
            return fn()
        except OSError:
            return fallback

    tf_alias = get_alias()
    tf_entry_points = ['thefuck', 'fuck']

    bins = [exe.name.decode('utf8') if six.PY2 else exe.name
            for path in os.environ.get('PATH', '').split(os.pathsep)
            if include_path_in_search(path)
            for exe in _safe(lambda: list(Path(path).iterdir()), [])
            if not _safe(exe.is_dir, True)
            and exe.name not in tf_entry_points]
    aliases = [alias.decode('utf8') if six.PY2 else alias
               for alias in shell.get_aliases() if alias != tf_alias]

    return bins + aliases


def replace_argument(script, from_, to):
    """Replaces command line argument."""
    replaced_in_the_end = re.sub(u' {}$'.format(re.escape(from_)), u' {}'.format(to),
                                 script, count=1)
    if replaced_in_the_end != script:
        return replaced_in_the_end
    else:
        return script.replace(
            u' {} '.format(from_), u' {} '.format(to), 1)


@decorator
def eager(fn, *args, **kwargs):
    return list(fn(*args, **kwargs))


@eager
def get_all_matched_commands(stderr, separator='Did you mean'):
    if not isinstance(separator, list):
        separator = [separator]
    should_yield = False
    for line in stderr.split('\n'):
        for sep in separator:
            if sep in line:
                should_yield = True
                break
        else:
            if should_yield and line:
                yield line.strip()


def replace_command(command, broken, matched):
    """Helper for *_no_command rules."""
    new_cmds = get_close_matches(broken, matched, cutoff=0.1)
    return [replace_argument(command.script, broken, new_cmd.strip())
            for new_cmd in new_cmds]


@memoize
def is_app(command, *app_names, **kwargs):
    """Returns `True` if command is call to one of passed app names."""

    at_least = kwargs.pop('at_least', 0)
    if kwargs:
        raise TypeError("got an unexpected keyword argument '{}'".format(kwargs.keys()))

    if len(command.script_parts) > at_least:
        return os.path.basename(command.script_parts[0]) in app_names

    return False


def for_app(*app_names, **kwargs):
    """Specifies that matching script is for one of app names."""
    def _for_app(fn, command):
        if is_app(command, *app_names, **kwargs):
            return fn(command)
        else:
            return False

    return decorator(_for_app)


class Cache(object):
    """Lazy read cache and save changes at exit."""

    def __init__(self):
        self._db = None

    def _init_db(self):
        try:
            self._setup_db()
        except Exception:
            exception("Unable to init cache", sys.exc_info())
            self._db = {}

    def _setup_db(self):
        cache_dir = self._get_cache_dir()
        cache_path = Path(cache_dir).joinpath('thefuck').as_posix()

        try:
            self._db = shelve.open(cache_path)
        except shelve_open_error + (ImportError,):
            # Caused when switching between Python versions
            warn("Removing possibly out-dated cache")
            os.remove(cache_path)
            self._db = shelve.open(cache_path)

        atexit.register(self._db.close)

    def _get_cache_dir(self):
        default_xdg_cache_dir = os.path.expanduser("~/.cache")
        cache_dir = os.getenv("XDG_CACHE_HOME", default_xdg_cache_dir)

        # Ensure the cache_path exists, Python 2 does not have the exist_ok
        # parameter
        try:
            os.makedirs(cache_dir)
        except OSError:
            if not os.path.isdir(cache_dir):
                raise

        return cache_dir

    def _get_mtime(self, path):
        try:
            return str(os.path.getmtime(path))
        except OSError:
            return '0'

    def _get_key(self, fn, depends_on, args, kwargs):
        parts = (fn.__module__, repr(fn).split('at')[0],
                 depends_on, args, kwargs)
        return str(pickle.dumps(parts))

    def get_value(self, fn, depends_on, args, kwargs):
        if self._db is None:
            self._init_db()

        depends_on = [Path(name).expanduser().absolute().as_posix()
                      for name in depends_on]
        key = self._get_key(fn, depends_on, args, kwargs)
        etag = '.'.join(self._get_mtime(path) for path in depends_on)

        if self._db.get(key, {}).get('etag') == etag:
            return self._db[key]['value']
        else:
            value = fn(*args, **kwargs)
            self._db[key] = {'etag': etag, 'value': value}
            return value


_cache = Cache()


def cache(*depends_on):
    """Caches function result in temporary file.

    Cache will be expired when modification date of files from `depends_on`
    will be changed.

    Only functions should be wrapped in `cache`, not methods.

    """
    def cache_decorator(fn):
        @memoize
        @wraps(fn)
        def wrapper(*args, **kwargs):
            if cache.disabled:
                return fn(*args, **kwargs)
            else:
                return _cache.get_value(fn, depends_on, args, kwargs)

        return wrapper

    return cache_decorator


cache.disabled = False


def get_installation_version():
    try:
        from importlib.metadata import version

        return version('thefuck')
    except ImportError:
        import pkg_resources

        return pkg_resources.require('thefuck')[0].version


def get_alias():
    return os.environ.get('TF_ALIAS', 'fuck')


@memoize
def get_valid_history_without_current(command):
    def _not_corrected(history, tf_alias):
        """Returns all lines from history except that comes before `fuck`."""
        previous = None
        for line in history:
            if previous is not None and line != tf_alias:
                yield previous
            previous = line
        if history:
            yield history[-1]

    from thefuck.shells import shell
    history = shell.get_history()
    tf_alias = get_alias()
    executables = set(get_all_executables())\
        .union(shell.get_builtin_commands())

    return [line for line in _not_corrected(history, tf_alias)
            if not line.startswith(tf_alias) and not line == command.script
            and line.split(' ')[0] in executables]


def format_raw_script(raw_script):
    """Creates single script from a list of script parts.

    :type raw_script: [basestring]
    :rtype: basestring

    """
    if six.PY2:
        script = ' '.join(arg.decode('utf-8') for arg in raw_script)
    else:
        script = ' '.join(raw_script)

    return script.lstrip()



File: thefuck/logs.py

# -*- encoding: utf-8 -*-

from contextlib import contextmanager
from datetime import datetime
import sys
from traceback import format_exception
import colorama
from .conf import settings
from . import const


def color(color_):
    """Utility for ability to disabling colored output."""
    if settings.no_colors:
        return ''
    else:
        return color_


def warn(title):
    sys.stderr.write(u'{warn}[WARN] {title}{reset}\n'.format(
        warn=color(colorama.Back.RED + colorama.Fore.WHITE
                   + colorama.Style.BRIGHT),
        reset=color(colorama.Style.RESET_ALL),
        title=title))


def exception(title, exc_info):
    sys.stderr.write(
        u'{warn}[WARN] {title}:{reset}\n{trace}'
        u'{warn}----------------------------{reset}\n\n'.format(
            warn=color(colorama.Back.RED + colorama.Fore.WHITE
                       + colorama.Style.BRIGHT),
            reset=color(colorama.Style.RESET_ALL),
            title=title,
            trace=''.join(format_exception(*exc_info))))


def rule_failed(rule, exc_info):
    exception(u'Rule {}'.format(rule.name), exc_info)


def failed(msg):
    sys.stderr.write(u'{red}{msg}{reset}\n'.format(
        msg=msg,
        red=color(colorama.Fore.RED),
        reset=color(colorama.Style.RESET_ALL)))


def show_corrected_command(corrected_command):
    sys.stderr.write(u'{prefix}{bold}{script}{reset}{side_effect}\n'.format(
        prefix=const.USER_COMMAND_MARK,
        script=corrected_command.script,
        side_effect=u' (+side effect)' if corrected_command.side_effect else u'',
        bold=color(colorama.Style.BRIGHT),
        reset=color(colorama.Style.RESET_ALL)))


def confirm_text(corrected_command):
    sys.stderr.write(
        (u'{prefix}{clear}{bold}{script}{reset}{side_effect} '
         u'[{green}enter{reset}/{blue}↑{reset}/{blue}↓{reset}'
         u'/{red}ctrl+c{reset}]').format(
            prefix=const.USER_COMMAND_MARK,
            script=corrected_command.script,
            side_effect=' (+side effect)' if corrected_command.side_effect else '',
            clear='\033[1K\r',
            bold=color(colorama.Style.BRIGHT),
            green=color(colorama.Fore.GREEN),
            red=color(colorama.Fore.RED),
            reset=color(colorama.Style.RESET_ALL),
            blue=color(colorama.Fore.BLUE)))


def debug(msg):
    if settings.debug:
        sys.stderr.write(u'{blue}{bold}DEBUG:{reset} {msg}\n'.format(
            msg=msg,
            reset=color(colorama.Style.RESET_ALL),
            blue=color(colorama.Fore.BLUE),
            bold=color(colorama.Style.BRIGHT)))


@contextmanager
def debug_time(msg):
    started = datetime.now()
    try:
        yield
    finally:
        debug(u'{} took: {}'.format(msg, datetime.now() - started))


def how_to_configure_alias(configuration_details):
    print(u"Seems like {bold}fuck{reset} alias isn't configured!".format(
        bold=color(colorama.Style.BRIGHT),
        reset=color(colorama.Style.RESET_ALL)))

    if configuration_details:
        print(
            u"Please put {bold}{content}{reset} in your "
            u"{bold}{path}{reset} and apply "
            u"changes with {bold}{reload}{reset} or restart your shell.".format(
                bold=color(colorama.Style.BRIGHT),
                reset=color(colorama.Style.RESET_ALL),
                **configuration_details._asdict()))

        if configuration_details.can_configure_automatically:
            print(
                u"Or run {bold}fuck{reset} a second time to configure"
                u" it automatically.".format(
                    bold=color(colorama.Style.BRIGHT),
                    reset=color(colorama.Style.RESET_ALL)))

    print(u'More details - https://github.com/nvbn/thefuck#manual-installation')


def already_configured(configuration_details):
    print(
        u"Seems like {bold}fuck{reset} alias already configured!\n"
        u"For applying changes run {bold}{reload}{reset}"
        u" or restart your shell.".format(
            bold=color(colorama.Style.BRIGHT),
            reset=color(colorama.Style.RESET_ALL),
            reload=configuration_details.reload))


def configured_successfully(configuration_details):
    print(
        u"{bold}fuck{reset} alias configured successfully!\n"
        u"For applying changes run {bold}{reload}{reset}"
        u" or restart your shell.".format(
            bold=color(colorama.Style.BRIGHT),
            reset=color(colorama.Style.RESET_ALL),
            reload=configuration_details.reload))


def version(thefuck_version, python_version, shell_info):
    sys.stderr.write(
        u'The Fuck {} using Python {} and {}\n'.format(thefuck_version,
                                                       python_version,
                                                       shell_info))



File: thefuck/exceptions.py

class EmptyCommand(Exception):
    """Raised when empty command passed to `thefuck`."""


class NoRuleMatched(Exception):
    """Raised when no rule matched for some command."""


class ScriptNotInLog(Exception):
    """Script not found in log."""



File: thefuck/const.py

# -*- encoding: utf-8 -*-


class _GenConst(object):
    def __init__(self, name):
        self._name = name

    def __repr__(self):
        return u'<const: {}>'.format(self._name)


KEY_UP = _GenConst('↑')
KEY_DOWN = _GenConst('↓')
KEY_CTRL_C = _GenConst('Ctrl+C')
KEY_CTRL_N = _GenConst('Ctrl+N')
KEY_CTRL_P = _GenConst('Ctrl+P')

KEY_MAPPING = {'\x0e': KEY_CTRL_N,
               '\x03': KEY_CTRL_C,
               '\x10': KEY_CTRL_P}

ACTION_SELECT = _GenConst('select')
ACTION_ABORT = _GenConst('abort')
ACTION_PREVIOUS = _GenConst('previous')
ACTION_NEXT = _GenConst('next')

ALL_ENABLED = _GenConst('All rules enabled')
DEFAULT_RULES = [ALL_ENABLED]
DEFAULT_PRIORITY = 1000

DEFAULT_SETTINGS = {'rules': DEFAULT_RULES,
                    'exclude_rules': [],
                    'wait_command': 3,
                    'require_confirmation': True,
                    'no_colors': False,
                    'debug': False,
                    'priority': {},
                    'history_limit': None,
                    'alter_history': True,
                    'wait_slow_command': 15,
                    'slow_commands': ['lein', 'react-native', 'gradle',
                                      './gradlew', 'vagrant'],
                    'repeat': False,
                    'instant_mode': False,
                    'num_close_matches': 3,
                    'env': {'LC_ALL': 'C', 'LANG': 'C', 'GIT_TRACE': '1'},
                    'excluded_search_path_prefixes': []}

ENV_TO_ATTR = {'THEFUCK_RULES': 'rules',
               'THEFUCK_EXCLUDE_RULES': 'exclude_rules',
               'THEFUCK_WAIT_COMMAND': 'wait_command',
               'THEFUCK_REQUIRE_CONFIRMATION': 'require_confirmation',
               'THEFUCK_NO_COLORS': 'no_colors',
               'THEFUCK_DEBUG': 'debug',
               'THEFUCK_PRIORITY': 'priority',
               'THEFUCK_HISTORY_LIMIT': 'history_limit',
               'THEFUCK_ALTER_HISTORY': 'alter_history',
               'THEFUCK_WAIT_SLOW_COMMAND': 'wait_slow_command',
               'THEFUCK_SLOW_COMMANDS': 'slow_commands',
               'THEFUCK_REPEAT': 'repeat',
               'THEFUCK_INSTANT_MODE': 'instant_mode',
               'THEFUCK_NUM_CLOSE_MATCHES': 'num_close_matches',
               'THEFUCK_EXCLUDED_SEARCH_PATH_PREFIXES': 'excluded_search_path_prefixes'}

SETTINGS_HEADER = u"""# The Fuck settings file
#
# The rules are defined as in the example bellow:
#
# rules = ['cd_parent', 'git_push', 'python_command', 'sudo']
#
# The default values are as follows. Uncomment and change to fit your needs.
# See https://github.com/nvbn/thefuck#settings for more information.
#

"""

ARGUMENT_PLACEHOLDER = 'THEFUCK_ARGUMENT_PLACEHOLDER'

CONFIGURATION_TIMEOUT = 60

USER_COMMAND_MARK = u'\u200B' * 10

LOG_SIZE_IN_BYTES = 1024 * 1024

LOG_SIZE_TO_CLEAN = 10 * 1024

DIFF_WITH_ALIAS = 0.5

SHELL_LOGGER_SOCKET_ENV = 'SHELL_LOGGER_SOCKET'

SHELL_LOGGER_LIMIT = 5



File: thefuck/argument_parser.py

import sys
from argparse import ArgumentParser, SUPPRESS
from .const import ARGUMENT_PLACEHOLDER
from .utils import get_alias


class Parser(object):
    """Argument parser that can handle arguments with our special
    placeholder.

    """

    def __init__(self):
        self._parser = ArgumentParser(prog='thefuck', add_help=False)
        self._add_arguments()

    def _add_arguments(self):
        """Adds arguments to parser."""
        self._parser.add_argument(
            '-v', '--version',
            action='store_true',
            help="show program's version number and exit")
        self._parser.add_argument(
            '-a', '--alias',
            nargs='?',
            const=get_alias(),
            help='[custom-alias-name] prints alias for current shell')
        self._parser.add_argument(
            '-l', '--shell-logger',
            action='store',
            help='log shell output to the file')
        self._parser.add_argument(
            '--enable-experimental-instant-mode',
            action='store_true',
            help='enable experimental instant mode, use on your own risk')
        self._parser.add_argument(
            '-h', '--help',
            action='store_true',
            help='show this help message and exit')
        self._add_conflicting_arguments()
        self._parser.add_argument(
            '-d', '--debug',
            action='store_true',
            help='enable debug output')
        self._parser.add_argument(
            '--force-command',
            action='store',
            help=SUPPRESS)
        self._parser.add_argument(
            'command',
            nargs='*',
            help='command that should be fixed')

    def _add_conflicting_arguments(self):
        """It's too dangerous to use `-y` and `-r` together."""
        group = self._parser.add_mutually_exclusive_group()
        group.add_argument(
            '-y', '--yes', '--yeah', '--hard',
            action='store_true',
            help='execute fixed command without confirmation')
        group.add_argument(
            '-r', '--repeat',
            action='store_true',
            help='repeat on failure')

    def _prepare_arguments(self, argv):
        """Prepares arguments by:

        - removing placeholder and moving arguments after it to beginning,
          we need this to distinguish arguments from `command` with ours;

        - adding `--` before `command`, so our parse would ignore arguments
          of `command`.

        """
        if ARGUMENT_PLACEHOLDER in argv:
            index = argv.index(ARGUMENT_PLACEHOLDER)
            return argv[index + 1:] + ['--'] + argv[:index]
        elif argv and not argv[0].startswith('-') and argv[0] != '--':
            return ['--'] + argv
        else:
            return argv

    def parse(self, argv):
        arguments = self._prepare_arguments(argv[1:])
        return self._parser.parse_args(arguments)

    def print_usage(self):
        self._parser.print_usage(sys.stderr)

    def print_help(self):
        self._parser.print_help(sys.stderr)



File: thefuck/specific/git.py

import re
from decorator import decorator
from ..utils import is_app
from ..shells import shell


@decorator
def git_support(fn, command):
    """Resolves git aliases and supports testing for both git and hub."""
    # supports GitHub's `hub` command
    # which is recommended to be used with `alias git=hub`
    # but at this point, shell aliases have already been resolved
    if not is_app(command, 'git', 'hub'):
        return False

    # perform git aliases expansion
    if command.output and 'trace: alias expansion:' in command.output:
        search = re.search("trace: alias expansion: ([^ ]*) => ([^\n]*)",
                           command.output)
        alias = search.group(1)

        # by default git quotes everything, for example:
        #     'commit' '--amend'
        # which is surprising and does not allow to easily test for
        # eg. 'git commit'
        expansion = ' '.join(shell.quote(part)
                             for part in shell.split_command(search.group(2)))
        new_script = re.sub(r"\b{}\b".format(alias), expansion, command.script)

        command = command.update(script=new_script)

    return fn(command)



File: thefuck/specific/nix.py

from thefuck.utils import which

nix_available = bool(which('nix'))



File: thefuck/specific/sudo.py

import six
from decorator import decorator


@decorator
def sudo_support(fn, command):
    """Removes sudo before calling fn and adds it after."""
    if not command.script.startswith('sudo '):
        return fn(command)

    result = fn(command.update(script=command.script[5:]))

    if result and isinstance(result, six.string_types):
        return u'sudo {}'.format(result)
    elif isinstance(result, list):
        return [u'sudo {}'.format(x) for x in result]
    else:
        return result



File: thefuck/specific/__init__.py




File: thefuck/specific/npm.py

import re
from subprocess import Popen, PIPE
from thefuck.utils import memoize, eager, which

npm_available = bool(which('npm'))


@memoize
@eager
def get_scripts():
    """Get custom npm scripts."""
    proc = Popen(['npm', 'run-script'], stdout=PIPE)
    should_yeild = False
    for line in proc.stdout.readlines():
        line = line.decode()
        if 'available via `npm run-script`:' in line:
            should_yeild = True
            continue

        if should_yeild and re.match(r'^  [^ ]+', line):
            yield line.strip().split(' ')[0]



File: thefuck/specific/yum.py

from thefuck.utils import which

yum_available = bool(which('yum'))



File: thefuck/specific/dnf.py

from thefuck.utils import which

dnf_available = bool(which('dnf'))



File: thefuck/specific/archlinux.py

""" This file provide some utility functions for Arch Linux specific rules."""
import subprocess
from .. import utils


@utils.memoize
def get_pkgfile(command):
    """ Gets the packages that provide the given command using `pkgfile`.

    If the command is of the form `sudo foo`, searches for the `foo` command
    instead.
    """
    try:
        command = command.strip()

        if command.startswith('sudo '):
            command = command[5:]

        command = command.split(" ")[0]

        packages = subprocess.check_output(
            ['pkgfile', '-b', '-v', command],
            universal_newlines=True, stderr=utils.DEVNULL
        ).splitlines()

        return [package.split()[0] for package in packages]
    except subprocess.CalledProcessError as err:
        if err.returncode == 1 and err.output == "":
            return []
        else:
            raise err


def archlinux_env():
    if utils.which('yay'):
        pacman = 'yay'
    elif utils.which('pikaur'):
        pacman = 'pikaur'
    elif utils.which('yaourt'):
        pacman = 'yaourt'
    elif utils.which('pacman'):
        pacman = 'sudo pacman'
    else:
        return False, None

    enabled_by_default = utils.which('pkgfile')

    return enabled_by_default, pacman



File: thefuck/specific/apt.py

from thefuck.utils import which

apt_available = bool(which('apt-get'))



File: thefuck/specific/brew.py

import subprocess
from ..utils import memoize, which


brew_available = bool(which('brew'))


@memoize
def get_brew_path_prefix():
    """To get brew path"""
    try:
        return subprocess.check_output(['brew', '--prefix'],
                                       universal_newlines=True).strip()
    except Exception:
        return None



File: thefuck/output_readers/__init__.py

from ..conf import settings
from . import read_log, rerun, shell_logger


def get_output(script, expanded):
    """Get output of the script.

    :param script: Console script.
    :type script: str
    :param expanded: Console script with expanded aliases.
    :type expanded: str
    :rtype: str

    """
    if shell_logger.is_available():
        return shell_logger.get_output(script)
    if settings.instant_mode:
        return read_log.get_output(script)
    else:
        return rerun.get_output(script, expanded)



File: thefuck/output_readers/rerun.py

import os
import shlex
import six
from subprocess import Popen, PIPE, STDOUT
from psutil import AccessDenied, Process, TimeoutExpired
from .. import logs
from ..conf import settings


def _kill_process(proc):
    """Tries to kill the process otherwise just logs a debug message, the
    process will be killed when thefuck terminates.

    :type proc: Process

    """
    try:
        proc.kill()
    except AccessDenied:
        logs.debug(u'Rerun: process PID {} ({}) could not be terminated'.format(
            proc.pid, proc.exe()))


def _wait_output(popen, is_slow):
    """Returns `True` if we can get output of the command in the
    `settings.wait_command` time.

    Command will be killed if it wasn't finished in the time.

    :type popen: Popen
    :rtype: bool

    """
    proc = Process(popen.pid)
    try:
        proc.wait(settings.wait_slow_command if is_slow
                  else settings.wait_command)
        return True
    except TimeoutExpired:
        for child in proc.children(recursive=True):
            _kill_process(child)
        _kill_process(proc)
        return False


def get_output(script, expanded):
    """Runs the script and obtains stdin/stderr.

    :type script: str
    :type expanded: str
    :rtype: str | None

    """
    env = dict(os.environ)
    env.update(settings.env)

    if six.PY2:
        expanded = expanded.encode('utf-8')

    split_expand = shlex.split(expanded)
    is_slow = split_expand[0] in settings.slow_commands if split_expand else False
    with logs.debug_time(u'Call: {}; with env: {}; is slow: {}'.format(
            script, env, is_slow)):
        result = Popen(expanded, shell=True, stdin=PIPE,
                       stdout=PIPE, stderr=STDOUT, env=env)
        if _wait_output(result, is_slow):
            output = result.stdout.read().decode('utf-8', errors='replace')
            logs.debug(u'Received output: {}'.format(output))
            return output
        else:
            logs.debug(u'Execution timed out!')
            return None



File: thefuck/output_readers/read_log.py

import os
import shlex
import mmap
import re
try:
    from shutil import get_terminal_size
except ImportError:
    from backports.shutil_get_terminal_size import get_terminal_size
import six
import pyte
from ..exceptions import ScriptNotInLog
from .. import const, logs


def _group_by_calls(log):
    ps1 = os.environ['PS1']
    ps1_newlines = ps1.count('\\n') + ps1.count('\n')
    ps1_counter = 0

    script_line = None
    lines = []
    for line in log:
        if const.USER_COMMAND_MARK in line or ps1_counter > 0:
            if script_line and ps1_counter == 0:
                yield script_line, lines

            if ps1_newlines > 0:
                if ps1_counter <= 0:
                    ps1_counter = ps1_newlines
                else:
                    ps1_counter -= 1

            script_line = line
            lines = [line]
        elif script_line is not None:
            lines.append(line)

    if script_line:
        yield script_line, lines


def _get_script_group_lines(grouped, script):
    if six.PY2:
        script = script.encode('utf-8')

    parts = shlex.split(script)

    for script_line, lines in reversed(grouped):
        if all(part in script_line for part in parts):
            return lines

    raise ScriptNotInLog


def _get_output_lines(script, log_file):
    data = log_file.read().decode()
    data = re.sub(r'\x00+$', '', data)
    lines = data.split('\n')
    grouped = list(_group_by_calls(lines))
    script_lines = _get_script_group_lines(grouped, script)
    screen = pyte.Screen(get_terminal_size().columns, len(script_lines))
    stream = pyte.Stream(screen)
    stream.feed('\n'.join(script_lines))
    return screen.display


def _skip_old_lines(log_file):
    size = os.path.getsize(os.environ['THEFUCK_OUTPUT_LOG'])
    if size > const.LOG_SIZE_IN_BYTES:
        log_file.seek(size - const.LOG_SIZE_IN_BYTES)


def get_output(script):
    """Reads script output from log.

    :type script: str
    :rtype: str | None

    """
    if six.PY2:
        logs.warn('Experimental instant mode is Python 3+ only')
        return None

    if 'THEFUCK_OUTPUT_LOG' not in os.environ:
        logs.warn("Output log isn't specified")
        return None

    if const.USER_COMMAND_MARK not in os.environ.get('PS1', ''):
        logs.warn(
            "PS1 doesn't contain user command mark, please ensure "
            "that PS1 is not changed after The Fuck alias initialization")
        return None

    try:
        with logs.debug_time(u'Read output from log'):
            fd = os.open(os.environ['THEFUCK_OUTPUT_LOG'], os.O_RDONLY)
            buffer = mmap.mmap(fd, const.LOG_SIZE_IN_BYTES, mmap.MAP_SHARED, mmap.PROT_READ)
            _skip_old_lines(buffer)
            lines = _get_output_lines(script, buffer)
            output = '\n'.join(lines).strip()
            logs.debug(u'Received output: {}'.format(output))
            return output
    except OSError:
        logs.warn("Can't read output log")
        return None
    except ScriptNotInLog:
        logs.warn("Script not found in output log")
        return None



File: thefuck/output_readers/shell_logger.py

import json
import os
import socket
try:
    from shutil import get_terminal_size
except ImportError:
    from backports.shutil_get_terminal_size import get_terminal_size
import pyte
from .. import const, logs


def _get_socket_path():
    return os.environ.get(const.SHELL_LOGGER_SOCKET_ENV)


def is_available():
    """Returns `True` if shell logger socket available.

    :rtype: book

    """
    path = _get_socket_path()
    if not path:
        return False

    return os.path.exists(path)


def _get_last_n(n):
    with socket.socket(socket.AF_UNIX) as client:
        client.connect(_get_socket_path())
        request = json.dumps({
            "type": "list",
            "count": n,
        }) + '\n'
        client.sendall(request.encode('utf-8'))
        response = client.makefile().readline()
        return json.loads(response)['commands']


def _get_output_lines(output):
    lines = output.split('\n')
    screen = pyte.Screen(get_terminal_size().columns, len(lines))
    stream = pyte.Stream(screen)
    stream.feed('\n'.join(lines))
    return screen.display


def get_output(script):
    """Gets command output from shell logger."""
    with logs.debug_time(u'Read output from external shell logger'):
        commands = _get_last_n(const.SHELL_LOGGER_LIMIT)
        for command in commands:
            if command['command'] == script:
                lines = _get_output_lines(command['output'])
                output = '\n'.join(lines).strip()
                return output
            else:
                logs.warn("Output isn't available in shell logger")
                return None



File: thefuck/shells/tcsh.py

from subprocess import Popen, PIPE
from time import time
import os
from ..utils import DEVNULL, memoize
from .generic import Generic


class Tcsh(Generic):
    friendly_name = 'Tcsh'

    def app_alias(self, alias_name):
        return ("alias {0} 'setenv TF_SHELL tcsh && setenv TF_ALIAS {0} && "
                "set fucked_cmd=`history -h 2 | head -n 1` && "
                "eval `thefuck ${{fucked_cmd}}`'").format(alias_name)

    def _parse_alias(self, alias):
        name, value = alias.split("\t", 1)
        return name, value

    @memoize
    def get_aliases(self):
        proc = Popen(['tcsh', '-ic', 'alias'], stdout=PIPE, stderr=DEVNULL)
        return dict(
            self._parse_alias(alias)
            for alias in proc.stdout.read().decode('utf-8').split('\n')
            if alias and '\t' in alias)

    def _get_history_file_name(self):
        return os.environ.get("HISTFILE",
                              os.path.expanduser('~/.history'))

    def _get_history_line(self, command_script):
        return u'#+{}\n{}\n'.format(int(time()), command_script)

    def how_to_configure(self):
        return self._create_shell_configuration(
            content=u'eval `thefuck --alias`',
            path='~/.tcshrc',
            reload='tcsh')

    def _get_version(self):
        """Returns the version of the current shell"""
        proc = Popen(['tcsh', '--version'], stdout=PIPE, stderr=DEVNULL)
        return proc.stdout.read().decode('utf-8').split()[1]



File: thefuck/shells/powershell.py

from subprocess import Popen, PIPE
from ..utils import DEVNULL
from .generic import Generic, ShellConfiguration


class Powershell(Generic):
    friendly_name = 'PowerShell'

    def app_alias(self, alias_name):
        return 'function ' + alias_name + ' {\n' \
               '    $history = (Get-History -Count 1).CommandLine;\n' \
               '    if (-not [string]::IsNullOrWhiteSpace($history)) {\n' \
               '        $fuck = $(thefuck $args $history);\n' \
               '        if (-not [string]::IsNullOrWhiteSpace($fuck)) {\n' \
               '            if ($fuck.StartsWith("echo")) { $fuck = $fuck.Substring(5); }\n' \
               '            else { iex "$fuck"; }\n' \
               '        }\n' \
               '    }\n' \
               '    [Console]::ResetColor() \n' \
               '}\n'

    def and_(self, *commands):
        return u' -and '.join('({0})'.format(c) for c in commands)

    def how_to_configure(self):
        return ShellConfiguration(
            content=u'iex "$(thefuck --alias)"',
            path='$profile',
            reload='. $profile',
            can_configure_automatically=False)

    def _get_version(self):
        """Returns the version of the current shell"""
        try:
            proc = Popen(
                ['powershell.exe', '$PSVersionTable.PSVersion'],
                stdout=PIPE,
                stderr=DEVNULL)
            version = proc.stdout.read().decode('utf-8').rstrip().split('\n')
            return '.'.join(version[-1].split())
        except IOError:
            proc = Popen(['pwsh', '--version'], stdout=PIPE, stderr=DEVNULL)
            return proc.stdout.read().decode('utf-8').split()[-1]



File: thefuck/shells/generic.py

import io
import os
import shlex
import six
from collections import namedtuple
from ..logs import warn
from ..utils import memoize
from ..conf import settings
from ..system import Path


ShellConfiguration = namedtuple('ShellConfiguration', (
    'content', 'path', 'reload', 'can_configure_automatically'))


class Generic(object):
    friendly_name = 'Generic Shell'

    def get_aliases(self):
        return {}

    def _expand_aliases(self, command_script):
        aliases = self.get_aliases()
        binary = command_script.split(' ')[0]
        if binary in aliases:
            return command_script.replace(binary, aliases[binary], 1)
        else:
            return command_script

    def from_shell(self, command_script):
        """Prepares command before running in app."""
        return self._expand_aliases(command_script)

    def to_shell(self, command_script):
        """Prepares command for running in shell."""
        return command_script

    def app_alias(self, alias_name):
        return """alias {0}='eval "$(TF_ALIAS={0} PYTHONIOENCODING=utf-8 """ \
               """thefuck "$(fc -ln -1)")"'""".format(alias_name)

    def instant_mode_alias(self, alias_name):
        warn("Instant mode not supported by your shell")
        return self.app_alias(alias_name)

    def _get_history_file_name(self):
        return ''

    def _get_history_line(self, command_script):
        return ''

    @memoize
    def get_history(self):
        return list(self._get_history_lines())

    def _get_history_lines(self):
        """Returns list of history entries."""
        history_file_name = self._get_history_file_name()
        if os.path.isfile(history_file_name):
            with io.open(history_file_name, 'r',
                         encoding='utf-8', errors='ignore') as history_file:

                lines = history_file.readlines()
                if settings.history_limit:
                    lines = lines[-settings.history_limit:]

                for line in lines:
                    prepared = self._script_from_history(line) \
                        .strip()
                    if prepared:
                        yield prepared

    def and_(self, *commands):
        return u' && '.join(commands)

    def or_(self, *commands):
        return u' || '.join(commands)

    def how_to_configure(self):
        return

    def split_command(self, command):
        """Split the command using shell-like syntax."""
        encoded = self.encode_utf8(command)

        try:
            splitted = [s.replace("??", "\\ ") for s in shlex.split(encoded.replace('\\ ', '??'))]
        except ValueError:
            splitted = encoded.split(' ')

        return self.decode_utf8(splitted)

    def encode_utf8(self, command):
        if six.PY2:
            return command.encode('utf8')
        return command

    def decode_utf8(self, command_parts):
        if six.PY2:
            return [s.decode('utf8') for s in command_parts]
        return command_parts

    def quote(self, s):
        """Return a shell-escaped version of the string s."""

        if six.PY2:
            from pipes import quote
        else:
            from shlex import quote

        return quote(s)

    def _script_from_history(self, line):
        return line

    def put_to_history(self, command):
        """Adds fixed command to shell history.

        In most of shells we change history on shell-level, but not
        all shells support it (Fish).

        """

    def get_builtin_commands(self):
        """Returns shells builtin commands."""
        return ['alias', 'bg', 'bind', 'break', 'builtin', 'case', 'cd',
                'command', 'compgen', 'complete', 'continue', 'declare',
                'dirs', 'disown', 'echo', 'enable', 'eval', 'exec', 'exit',
                'export', 'fc', 'fg', 'getopts', 'hash', 'help', 'history',
                'if', 'jobs', 'kill', 'let', 'local', 'logout', 'popd',
                'printf', 'pushd', 'pwd', 'read', 'readonly', 'return', 'set',
                'shift', 'shopt', 'source', 'suspend', 'test', 'times', 'trap',
                'type', 'typeset', 'ulimit', 'umask', 'unalias', 'unset',
                'until', 'wait', 'while']

    def _get_version(self):
        """Returns the version of the current shell"""
        return ''

    def info(self):
        """Returns the name and version of the current shell"""
        try:
            version = self._get_version()
        except Exception as e:
            warn(u'Could not determine shell version: {}'.format(e))
            version = ''
        return u'{} {}'.format(self.friendly_name, version).rstrip()

    def _create_shell_configuration(self, content, path, reload):
        return ShellConfiguration(
            content=content,
            path=path,
            reload=reload,
            can_configure_automatically=Path(path).expanduser().exists())



File: thefuck/shells/bash.py

import os
from subprocess import Popen, PIPE
from tempfile import gettempdir
from uuid import uuid4
from ..conf import settings
from ..const import ARGUMENT_PLACEHOLDER, USER_COMMAND_MARK
from ..utils import DEVNULL, memoize
from .generic import Generic


class Bash(Generic):
    friendly_name = 'Bash'

    def app_alias(self, alias_name):
        # It is VERY important to have the variables declared WITHIN the function
        return '''
            function {name} () {{
                TF_PYTHONIOENCODING=$PYTHONIOENCODING;
                export TF_SHELL=bash;
                export TF_ALIAS={name};
                export TF_SHELL_ALIASES=$(alias);
                export TF_HISTORY=$(fc -ln -10);
                export PYTHONIOENCODING=utf-8;
                TF_CMD=$(
                    thefuck {argument_placeholder} "$@"
                ) && eval "$TF_CMD";
                unset TF_HISTORY;
                export PYTHONIOENCODING=$TF_PYTHONIOENCODING;
                {alter_history}
            }}
        '''.format(
            name=alias_name,
            argument_placeholder=ARGUMENT_PLACEHOLDER,
            alter_history=('history -s $TF_CMD;'
                           if settings.alter_history else ''))

    def instant_mode_alias(self, alias_name):
        if os.environ.get('THEFUCK_INSTANT_MODE', '').lower() == 'true':
            mark = USER_COMMAND_MARK + '\b' * len(USER_COMMAND_MARK)
            return '''
                export PS1="{user_command_mark}$PS1";
                {app_alias}
            '''.format(user_command_mark=mark,
                       app_alias=self.app_alias(alias_name))
        else:
            log_path = os.path.join(
                gettempdir(), 'thefuck-script-log-{}'.format(uuid4().hex))
            return '''
                export THEFUCK_INSTANT_MODE=True;
                export THEFUCK_OUTPUT_LOG={log};
                thefuck --shell-logger {log};
                rm {log};
                exit
            '''.format(log=log_path)

    def _parse_alias(self, alias):
        name, value = alias.replace('alias ', '', 1).split('=', 1)
        if value[0] == value[-1] == '"' or value[0] == value[-1] == "'":
            value = value[1:-1]
        return name, value

    @memoize
    def get_aliases(self):
        raw_aliases = os.environ.get('TF_SHELL_ALIASES', '').split('\n')
        return dict(self._parse_alias(alias)
                    for alias in raw_aliases if alias and '=' in alias)

    def _get_history_file_name(self):
        return os.environ.get("HISTFILE",
                              os.path.expanduser('~/.bash_history'))

    def _get_history_line(self, command_script):
        return u'{}\n'.format(command_script)

    def how_to_configure(self):
        if os.path.join(os.path.expanduser('~'), '.bashrc'):
            config = '~/.bashrc'
        elif os.path.join(os.path.expanduser('~'), '.bash_profile'):
            config = '~/.bash_profile'
        else:
            config = 'bash config'

        return self._create_shell_configuration(
            content=u'eval "$(thefuck --alias)"',
            path=config,
            reload=u'source {}'.format(config))

    def _get_version(self):
        """Returns the version of the current shell"""
        proc = Popen(['bash', '-c', 'echo $BASH_VERSION'],
                     stdout=PIPE, stderr=DEVNULL)
        return proc.stdout.read().decode('utf-8').strip()



File: thefuck/shells/__init__.py

"""Package with shell specific actions, each shell class should
implement `from_shell`, `to_shell`, `app_alias`, `put_to_history` and
`get_aliases` methods.
"""
import os
from psutil import Process
from .bash import Bash
from .fish import Fish
from .generic import Generic
from .tcsh import Tcsh
from .zsh import Zsh
from .powershell import Powershell

shells = {'bash': Bash,
          'fish': Fish,
          'zsh': Zsh,
          'csh': Tcsh,
          'tcsh': Tcsh,
          'powershell': Powershell,
          'pwsh': Powershell}


def _get_shell_from_env():
    name = os.environ.get('TF_SHELL')

    if name in shells:
        return shells[name]()


def _get_shell_from_proc():
    proc = Process(os.getpid())

    while proc is not None and proc.pid > 0:
        try:
            name = proc.name()
        except TypeError:
            name = proc.name

        name = os.path.splitext(name)[0]

        if name in shells:
            return shells[name]()

        try:
            proc = proc.parent()
        except TypeError:
            proc = proc.parent

    return Generic()


shell = _get_shell_from_env() or _get_shell_from_proc()



File: thefuck/shells/zsh.py

from time import time
import os
from subprocess import Popen, PIPE
from tempfile import gettempdir
from uuid import uuid4
from ..conf import settings
from ..const import ARGUMENT_PLACEHOLDER, USER_COMMAND_MARK
from ..utils import DEVNULL, memoize
from .generic import Generic


class Zsh(Generic):
    friendly_name = 'ZSH'

    def app_alias(self, alias_name):
        # It is VERY important to have the variables declared WITHIN the function
        return '''
            {name} () {{
                TF_PYTHONIOENCODING=$PYTHONIOENCODING;
                export TF_SHELL=zsh;
                export TF_ALIAS={name};
                TF_SHELL_ALIASES=$(alias);
                export TF_SHELL_ALIASES;
                TF_HISTORY="$(fc -ln -10)";
                export TF_HISTORY;
                export PYTHONIOENCODING=utf-8;
                TF_CMD=$(
                    thefuck {argument_placeholder} $@
                ) && eval $TF_CMD;
                unset TF_HISTORY;
                export PYTHONIOENCODING=$TF_PYTHONIOENCODING;
                {alter_history}
            }}
        '''.format(
            name=alias_name,
            argument_placeholder=ARGUMENT_PLACEHOLDER,
            alter_history=('test -n "$TF_CMD" && print -s $TF_CMD'
                           if settings.alter_history else ''))

    def instant_mode_alias(self, alias_name):
        if os.environ.get('THEFUCK_INSTANT_MODE', '').lower() == 'true':
            mark = ('%{' +
                    USER_COMMAND_MARK + '\b' * len(USER_COMMAND_MARK)
                    + '%}')
            return '''
                export PS1="{user_command_mark}$PS1";
                {app_alias}
            '''.format(user_command_mark=mark,
                       app_alias=self.app_alias(alias_name))
        else:
            log_path = os.path.join(
                gettempdir(), 'thefuck-script-log-{}'.format(uuid4().hex))
            return '''
                export THEFUCK_INSTANT_MODE=True;
                export THEFUCK_OUTPUT_LOG={log};
                thefuck --shell-logger {log};
                rm -f {log};
                exit
            '''.format(log=log_path)

    def _parse_alias(self, alias):
        name, value = alias.split('=', 1)
        if value[0] == value[-1] == '"' or value[0] == value[-1] == "'":
            value = value[1:-1]
        return name, value

    @memoize
    def get_aliases(self):
        raw_aliases = os.environ.get('TF_SHELL_ALIASES', '').split('\n')
        return dict(self._parse_alias(alias)
                    for alias in raw_aliases if alias and '=' in alias)

    def _get_history_file_name(self):
        return os.environ.get("HISTFILE",
                              os.path.expanduser('~/.zsh_history'))

    def _get_history_line(self, command_script):
        return u': {}:0;{}\n'.format(int(time()), command_script)

    def _script_from_history(self, line):
        if ';' in line:
            return line.split(';', 1)[1]
        else:
            return ''

    def how_to_configure(self):
        return self._create_shell_configuration(
            content=u'eval $(thefuck --alias)',
            path='~/.zshrc',
            reload='source ~/.zshrc')

    def _get_version(self):
        """Returns the version of the current shell"""
        proc = Popen(['zsh', '-c', 'echo $ZSH_VERSION'],
                     stdout=PIPE, stderr=DEVNULL)
        return proc.stdout.read().decode('utf-8').strip()



File: thefuck/shells/fish.py

from subprocess import Popen, PIPE
from time import time
import os
import sys
import six
from .. import logs
from ..conf import settings
from ..const import ARGUMENT_PLACEHOLDER
from ..utils import DEVNULL, cache
from .generic import Generic


@cache('~/.config/fish/config.fish', '~/.config/fish/functions')
def _get_functions(overridden):
    proc = Popen(['fish', '-ic', 'functions'], stdout=PIPE, stderr=DEVNULL)
    functions = proc.stdout.read().decode('utf-8').strip().split('\n')
    return {func: func for func in functions if func not in overridden}


@cache('~/.config/fish/config.fish')
def _get_aliases(overridden):
    aliases = {}
    proc = Popen(['fish', '-ic', 'alias'], stdout=PIPE, stderr=DEVNULL)
    alias_out = proc.stdout.read().decode('utf-8').strip()
    if not alias_out:
        return aliases
    for alias in alias_out.split('\n'):
        for separator in (' ', '='):
            split_alias = alias.replace('alias ', '', 1).split(separator, 1)
            if len(split_alias) == 2:
                name, value = split_alias
                break
        else:
            continue
        if name not in overridden:
            aliases[name] = value
    return aliases


class Fish(Generic):
    friendly_name = 'Fish Shell'

    def _get_overridden_aliases(self):
        overridden = os.environ.get('THEFUCK_OVERRIDDEN_ALIASES',
                                    os.environ.get('TF_OVERRIDDEN_ALIASES', ''))
        default = {'cd', 'grep', 'ls', 'man', 'open'}
        for alias in overridden.split(','):
            default.add(alias.strip())
        return sorted(default)

    def app_alias(self, alias_name):
        if settings.alter_history:
            alter_history = ('    builtin history delete --exact'
                             ' --case-sensitive -- $fucked_up_command\n'
                             '    builtin history merge\n')
        else:
            alter_history = ''
        # It is VERY important to have the variables declared WITHIN the alias
        return ('function {0} -d "Correct your previous console command"\n'
                '  set -l fucked_up_command $history[1]\n'
                '  env TF_SHELL=fish TF_ALIAS={0} PYTHONIOENCODING=utf-8'
                ' thefuck $fucked_up_command {2} $argv | read -l unfucked_command\n'
                '  if [ "$unfucked_command" != "" ]\n'
                '    eval $unfucked_command\n{1}'
                '  end\n'
                'end').format(alias_name, alter_history, ARGUMENT_PLACEHOLDER)

    def get_aliases(self):
        overridden = self._get_overridden_aliases()
        functions = _get_functions(overridden)
        raw_aliases = _get_aliases(overridden)
        functions.update(raw_aliases)
        return functions

    def _expand_aliases(self, command_script):
        aliases = self.get_aliases()
        binary = command_script.split(' ')[0]
        if binary in aliases and aliases[binary] != binary:
            return command_script.replace(binary, aliases[binary], 1)
        elif binary in aliases:
            return u'fish -ic "{}"'.format(command_script.replace('"', r'\"'))
        else:
            return command_script

    def _get_history_file_name(self):
        return os.path.expanduser('~/.config/fish/fish_history')

    def _get_history_line(self, command_script):
        return u'- cmd: {}\n   when: {}\n'.format(command_script, int(time()))

    def _script_from_history(self, line):
        if '- cmd: ' in line:
            return line.split('- cmd: ', 1)[1]
        else:
            return ''

    def and_(self, *commands):
        return u'; and '.join(commands)

    def or_(self, *commands):
        return u'; or '.join(commands)

    def how_to_configure(self):
        return self._create_shell_configuration(
            content=u"thefuck --alias | source",
            path='~/.config/fish/config.fish',
            reload='fish')

    def _get_version(self):
        """Returns the version of the current shell"""
        proc = Popen(['fish', '--version'], stdout=PIPE, stderr=DEVNULL)
        return proc.stdout.read().decode('utf-8').split()[-1]

    def put_to_history(self, command):
        try:
            return self._put_to_history(command)
        except IOError:
            logs.exception("Can't update history", sys.exc_info())

    def _put_to_history(self, command_script):
        """Puts command script to shell history."""
        history_file_name = self._get_history_file_name()
        if os.path.isfile(history_file_name):
            with open(history_file_name, 'a') as history:
                entry = self._get_history_line(command_script)
                if six.PY2:
                    history.write(entry.encode('utf-8'))
                else:
                    history.write(entry)



File: thefuck/system/unix.py

import os
import sys
import tty
import termios
import colorama
from distutils.spawn import find_executable
from .. import const

init_output = colorama.init


def getch():
    fd = sys.stdin.fileno()
    old = termios.tcgetattr(fd)
    try:
        tty.setraw(fd)
        return sys.stdin.read(1)
    finally:
        termios.tcsetattr(fd, termios.TCSADRAIN, old)


def get_key():
    ch = getch()

    if ch in const.KEY_MAPPING:
        return const.KEY_MAPPING[ch]
    elif ch == '\x1b':
        next_ch = getch()
        if next_ch == '[':
            last_ch = getch()

            if last_ch == 'A':
                return const.KEY_UP
            elif last_ch == 'B':
                return const.KEY_DOWN

    return ch


def open_command(arg):
    if find_executable('xdg-open'):
        return 'xdg-open ' + arg
    return 'open ' + arg


try:
    from pathlib import Path
except ImportError:
    from pathlib2 import Path


def _expanduser(self):
    return self.__class__(os.path.expanduser(str(self)))


if not hasattr(Path, 'expanduser'):
    Path.expanduser = _expanduser



File: thefuck/system/__init__.py

import sys


if sys.platform == 'win32':
    from .win32 import *  # noqa: F401,F403
else:
    from .unix import *  # noqa: F401,F403



File: thefuck/system/win32.py

import os
import msvcrt
import win_unicode_console
from .. import const


def init_output():
    import colorama
    win_unicode_console.enable()
    colorama.init()


def get_key():
    ch = msvcrt.getwch()
    if ch in ('\x00', '\xe0'):  # arrow or function key prefix?
        ch = msvcrt.getwch()  # second call returns the actual key code

    if ch in const.KEY_MAPPING:
        return const.KEY_MAPPING[ch]
    if ch == 'H':
        return const.KEY_UP
    if ch == 'P':
        return const.KEY_DOWN

    return ch


def open_command(arg):
    return 'cmd /c start ' + arg


try:
    from pathlib import Path
except ImportError:
    from pathlib2 import Path


def _expanduser(self):
    return self.__class__(os.path.expanduser(str(self)))


# pathlib's expanduser fails on windows, see http://bugs.python.org/issue19776
Path.expanduser = _expanduser



File: thefuck/rules/git_push.py

import re
from thefuck.utils import replace_argument
from thefuck.specific.git import git_support


@git_support
def match(command):
    return ('push' in command.script_parts
            and 'git push --set-upstream' in command.output)


def _get_upstream_option_index(command_parts):
    if '--set-upstream' in command_parts:
        return command_parts.index('--set-upstream')
    elif '-u' in command_parts:
        return command_parts.index('-u')
    else:
        return None


@git_support
def get_new_command(command):
    # If --set-upstream or -u are passed, remove it and its argument. This is
    # because the remaining arguments are concatenated onto the command suggested
    # by git, which includes --set-upstream and its argument
    command_parts = command.script_parts[:]
    upstream_option_index = _get_upstream_option_index(command_parts)

    if upstream_option_index is not None:
        command_parts.pop(upstream_option_index)

        # In case of `git push -u` we don't have next argument:
        if len(command_parts) > upstream_option_index:
            command_parts.pop(upstream_option_index)
    else:
        # the only non-qualified permitted options are the repository and refspec; git's
        # suggestion include them, so they won't be lost, but would be duplicated otherwise.
        push_idx = command_parts.index('push') + 1
        while len(command_parts) > push_idx and command_parts[len(command_parts) - 1][0] != '-':
            command_parts.pop(len(command_parts) - 1)

    arguments = re.findall(r'git push (.*)', command.output)[-1].replace("'", r"\'").strip()
    return replace_argument(" ".join(command_parts), 'push',
                            'push {}'.format(arguments))



File: thefuck/rules/git_bisect_usage.py

import re
from thefuck.utils import replace_command
from thefuck.specific.git import git_support


@git_support
def match(command):
    return ('bisect' in command.script_parts and
            'usage: git bisect' in command.output)


@git_support
def get_new_command(command):
    broken = re.findall(r'git bisect ([^ $]*).*', command.script)[0]
    usage = re.findall(r'usage: git bisect \[([^\]]+)\]', command.output)[0]
    return replace_command(command, broken, usage.split('|'))



File: thefuck/rules/yarn_alias.py

import re
from thefuck.utils import replace_argument, for_app


@for_app('yarn', at_least=1)
def match(command):
    return 'Did you mean' in command.output


def get_new_command(command):
    broken = command.script_parts[1]
    fix = re.findall(r'Did you mean [`"](?:yarn )?([^`"]*)[`"]', command.output)[0]

    return replace_argument(command.script, broken, fix)



File: thefuck/rules/git_pull_clone.py

from thefuck.utils import replace_argument
from thefuck.specific.git import git_support


@git_support
def match(command):
    return ('fatal: Not a git repository' in command.output
            and "Stopping at filesystem boundary (GIT_DISCOVERY_ACROSS_FILESYSTEM not set)." in command.output)


@git_support
def get_new_command(command):
    return replace_argument(command.script, 'pull', 'clone')



File: thefuck/rules/ssh_known_hosts.py

import re
from thefuck.utils import for_app

commands = ('ssh', 'scp')


@for_app(*commands)
def match(command):
    if not command.script:
        return False
    if not command.script.startswith(commands):
        return False

    patterns = (
        r'WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!',
        r'WARNING: POSSIBLE DNS SPOOFING DETECTED!',
        r"Warning: the \S+ host key for '([^']+)' differs from the key for the IP address '([^']+)'",
    )

    return any(re.findall(pattern, command.output) for pattern in patterns)


def get_new_command(command):
    return command.script


def side_effect(old_cmd, command):
    offending_pattern = re.compile(
        r'(?:Offending (?:key for IP|\S+ key)|Matching host key) in ([^:]+):(\d+)',
        re.MULTILINE)
    offending = offending_pattern.findall(old_cmd.output)
    for filepath, lineno in offending:
        with open(filepath, 'r') as fh:
            lines = fh.readlines()
            del lines[int(lineno) - 1]
        with open(filepath, 'w') as fh:
            fh.writelines(lines)



File: thefuck/rules/pacman.py

from thefuck.specific.archlinux import get_pkgfile, archlinux_env
from thefuck.shells import shell


def match(command):
    return 'not found' in command.output and get_pkgfile(command.script)


def get_new_command(command):
    packages = get_pkgfile(command.script)

    formatme = shell.and_('{} -S {}', '{}')
    return [formatme.format(pacman, package, command.script)
            for package in packages]


enabled_by_default, pacman = archlinux_env()



File: thefuck/rules/yum_invalid_operation.py

import subprocess
from itertools import dropwhile, islice, takewhile

from thefuck.specific.sudo import sudo_support
from thefuck.specific.yum import yum_available
from thefuck.utils import for_app, replace_command, which, cache

enabled_by_default = yum_available


@sudo_support
@for_app('yum')
def match(command):
    return 'No such command: ' in command.output


def _get_operations():
    proc = subprocess.Popen('yum', stdout=subprocess.PIPE)

    lines = proc.stdout.readlines()
    lines = [line.decode('utf-8') for line in lines]
    lines = dropwhile(lambda line: not line.startswith("List of Commands:"), lines)
    lines = islice(lines, 2, None)
    lines = list(takewhile(lambda line: line.strip(), lines))
    return [line.strip().split(' ')[0] for line in lines]


if which('yum'):
    _get_operations = cache(which('yum'))(_get_operations)


@sudo_support
def get_new_command(command):
    invalid_operation = command.script_parts[1]

    if invalid_operation == 'uninstall':
        return [command.script.replace('uninstall', 'remove')]

    return replace_command(command, invalid_operation, _get_operations())



File: thefuck/rules/ln_s_order.py

import os
from thefuck.specific.sudo import sudo_support


def _get_destination(script_parts):
    """When arguments order is wrong first argument will be destination."""
    for part in script_parts:
        if part not in {'ln', '-s', '--symbolic'} and os.path.exists(part):
            return part


@sudo_support
def match(command):
    return (command.script_parts[0] == 'ln'
            and {'-s', '--symbolic'}.intersection(command.script_parts)
            and 'File exists' in command.output
            and _get_destination(command.script_parts))


@sudo_support
def get_new_command(command):
    destination = _get_destination(command.script_parts)
    parts = command.script_parts[:]
    parts.remove(destination)
    parts.append(destination)
    return ' '.join(parts)



File: thefuck/rules/git_merge_unrelated.py

from thefuck.specific.git import git_support


@git_support
def match(command):
    return ('merge' in command.script
            and 'fatal: refusing to merge unrelated histories' in command.output)


@git_support
def get_new_command(command):
    return command.script + ' --allow-unrelated-histories'



File: thefuck/rules/java.py

"""Fixes common java command mistake

Example:
> java foo.java
Error: Could not find or load main class foo.java

"""
from thefuck.utils import for_app


@for_app('java')
def match(command):
    return command.script.endswith('.java')


def get_new_command(command):
    return command.script[:-5]



File: thefuck/rules/yarn_command_not_found.py

import re
from subprocess import Popen, PIPE
from thefuck.utils import (for_app, eager, replace_command, replace_argument,
                           cache, which)

regex = re.compile(r'error Command "(.*)" not found.')


@for_app('yarn')
def match(command):
    return regex.findall(command.output)


npm_commands = {'require': 'add'}


@eager
def _get_all_tasks():
    proc = Popen(['yarn', '--help'], stdout=PIPE)
    should_yield = False
    for line in proc.stdout.readlines():
        line = line.decode().strip()

        if 'Commands:' in line:
            should_yield = True
            continue

        if should_yield and '- ' in line:
            yield line.split(' ')[-1]


if which('yarn'):
    _get_all_tasks = cache(which('yarn'))(_get_all_tasks)


def get_new_command(command):
    misspelled_task = regex.findall(command.output)[0]
    if misspelled_task in npm_commands:
        yarn_command = npm_commands[misspelled_task]
        return replace_argument(command.script, misspelled_task, yarn_command)
    else:
        tasks = _get_all_tasks()
        return replace_command(command, misspelled_task, tasks)



File: thefuck/rules/apt_list_upgradable.py

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

enabled_by_default = apt_available


@sudo_support
@for_app('apt')
def match(command):
    return 'apt list --upgradable' in command.output


@sudo_support
def get_new_command(command):
    return 'apt list --upgradable'



File: thefuck/rules/git_commit_add.py

from thefuck.utils import eager, replace_argument
from thefuck.specific.git import git_support


@git_support
def match(command):
    return (
        "commit" in command.script_parts
        and "no changes added to commit" in command.output
    )


@eager
@git_support
def get_new_command(command):
    for opt in ("-a", "-p"):
        yield replace_argument(command.script, "commit", "commit {}".format(opt))



File: thefuck/rules/composer_not_command.py

import re
from thefuck.utils import replace_argument, for_app


@for_app('composer')
def match(command):
    return (('did you mean this?' in command.output.lower()
             or 'did you mean one of these?' in command.output.lower())) or (
        "install" in command.script_parts and "composer require" in command.output.lower()
    )


def get_new_command(command):
    if "install" in command.script_parts and "composer require" in command.output.lower():
        broken_cmd, new_cmd = "install", "require"
    else:
        broken_cmd = re.findall(r"Command \"([^']*)\" is not defined", command.output)[0]
        new_cmd = re.findall(r'Did you mean this\?[^\n]*\n\s*([^\n]*)', command.output)
        if not new_cmd:
            new_cmd = re.findall(r'Did you mean one of these\?[^\n]*\n\s*([^\n]*)', command.output)
        new_cmd = new_cmd[0].strip()
    return replace_argument(command.script, broken_cmd, new_cmd)



File: thefuck/rules/rails_migrations_pending.py

import re
from thefuck.shells import shell


SUGGESTION_REGEX = r"To resolve this issue, run:\s+(.*?)\n"


def match(command):
    return "Migrations are pending. To resolve this issue, run:" in command.output


def get_new_command(command):
    migration_script = re.search(SUGGESTION_REGEX, command.output).group(1)
    return shell.and_(migration_script, command.script)



File: thefuck/rules/git_flag_after_filename.py

import re
from thefuck.specific.git import git_support

error_pattern = "fatal: bad flag '(.*?)' used after filename"
error_pattern2 = "fatal: option '(.*?)' must come before non-option arguments"


@git_support
def match(command):
    return re.search(error_pattern, command.output) or re.search(error_pattern2, command.output)


@git_support
def get_new_command(command):
    command_parts = command.script_parts[:]

    # find the bad flag
    bad_flag = match(command).group(1)
    bad_flag_index = command_parts.index(bad_flag)

    # find the filename
    for index in reversed(range(bad_flag_index)):
        if command_parts[index][0] != '-':
            filename_index = index
            break

    # swap them
    command_parts[bad_flag_index], command_parts[filename_index] = \
    command_parts[filename_index], command_parts[bad_flag_index]  # noqa: E122

    return u' '.join(command_parts)



File: thefuck/rules/git_rm_local_modifications.py

from thefuck.specific.git import git_support


@git_support
def match(command):
    return (' rm ' in command.script and
            'error: the following file has local modifications' in command.output and
            'use --cached to keep the file, or -f to force removal' in command.output)


@git_support
def get_new_command(command):
    command_parts = command.script_parts[:]
    index = command_parts.index('rm') + 1
    command_parts.insert(index, '--cached')
    command_list = [u' '.join(command_parts)]
    command_parts[index] = '-f'
    command_list.append(u' '.join(command_parts))
    return command_list



File: thefuck/rules/git_stash.py

from thefuck.shells import shell
from thefuck.specific.git import git_support


@git_support
def match(command):
    # catches "Please commit or stash them" and "Please, commit your changes or
    # stash them before you can switch branches."
    return 'or stash them' in command.output


@git_support
def get_new_command(command):
    formatme = shell.and_('git stash', '{}')
    return formatme.format(command.script)



File: thefuck/rules/ls_all.py

from thefuck.utils import for_app


@for_app('ls')
def match(command):
    return command.output.strip() == ''


def get_new_command(command):
    return ' '.join(['ls', '-A'] + command.script_parts[1:])



File: thefuck/rules/lein_not_task.py

import re
from thefuck.utils import replace_command, get_all_matched_commands, for_app
from thefuck.specific.sudo import sudo_support


@sudo_support
@for_app('lein')
def match(command):
    return (command.script.startswith('lein')
            and "is not a task. See 'lein help'" in command.output
            and 'Did you mean this?' in command.output)


@sudo_support
def get_new_command(command):
    broken_cmd = re.findall(r"'([^']*)' is not a task",
                            command.output)[0]
    new_cmds = get_all_matched_commands(command.output, 'Did you mean this?')
    return replace_command(command, broken_cmd, new_cmds)



File: thefuck/rules/git_remote_seturl_add.py

from thefuck.utils import replace_argument
from thefuck.specific.git import git_support


@git_support
def match(command):
    return ('set-url' in command.script
            and 'fatal: No such remote' in command.output)


def get_new_command(command):
    return replace_argument(command.script, 'set-url', 'add')



File: thefuck/rules/git_commit_amend.py

from thefuck.specific.git import git_support


@git_support
def match(command):
    return ('commit' in command.script_parts)


@git_support
def get_new_command(command):
    return 'git commit --amend'



File: thefuck/rules/remove_trailing_cedilla.py

# -*- encoding: utf-8 -*-

CEDILLA = u"ç"


def match(command):
    return command.script.endswith(CEDILLA)


def get_new_command(command):
    return command.script[:-1]



File: thefuck/rules/pacman_not_found.py

""" Fixes wrong package names with pacman or yaourt.

For example the `llc` program is in package `llvm` so this:
    yay -S llc
should be:
    yay -S llvm
"""

from thefuck.utils import replace_command
from thefuck.specific.archlinux import get_pkgfile, archlinux_env


def match(command):
    return (command.script_parts
            and (command.script_parts[0] in ('pacman', 'yay', 'pikaur', 'yaourt')
                 or command.script_parts[0:2] == ['sudo', 'pacman'])
            and 'error: target not found:' in command.output)


def get_new_command(command):
    pgr = command.script_parts[-1]

    return replace_command(command, pgr, get_pkgfile(pgr))


enabled_by_default, _ = archlinux_env()



File: thefuck/rules/long_form_help.py

from thefuck.utils import replace_argument
import re

# regex to match a suggested help command from the tool output
help_regex = r"(?:Run|Try) '([^']+)'(?: or '[^']+')? for (?:details|more information)."


def match(command):
    if re.search(help_regex, command.output, re.I) is not None:
        return True

    if '--help' in command.output:
        return True

    return False


def get_new_command(command):
    if re.search(help_regex, command.output) is not None:
        match_obj = re.search(help_regex, command.output, re.I)
        return match_obj.group(1)

    return replace_argument(command.script, '-h', '--help')


enabled_by_default = True
priority = 5000



File: thefuck/rules/scm_correction.py

from thefuck.utils import for_app, memoize
from thefuck.system import Path

path_to_scm = {
    '.git': 'git',
    '.hg': 'hg',
}

wrong_scm_patterns = {
    'git': 'fatal: Not a git repository',
    'hg': 'abort: no repository found',
}


@memoize
def _get_actual_scm():
    for path, scm in path_to_scm.items():
        if Path(path).is_dir():
            return scm


@for_app(*wrong_scm_patterns.keys())
def match(command):
    scm = command.script_parts[0]
    pattern = wrong_scm_patterns[scm]

    return pattern in command.output and _get_actual_scm()


def get_new_command(command):
    scm = _get_actual_scm()
    return u' '.join([scm] + command.script_parts[1:])



File: thefuck/rules/git_push_pull.py

from thefuck.shells import shell
from thefuck.utils import replace_argument
from thefuck.specific.git import git_support


@git_support
def match(command):
    return ('push' in command.script and
            '! [rejected]' in command.output and
            'failed to push some refs to' in command.output and
            ('Updates were rejected because the tip of your'
             ' current branch is behind' in command.output or
             'Updates were rejected because the remote '
             'contains work that you do' in command.output))


@git_support
def get_new_command(command):
    return shell.and_(replace_argument(command.script, 'push', 'pull'),
                      command.script)



File: thefuck/rules/django_south_merge.py

def match(command):
    return 'manage.py' in command.script and \
           'migrate' in command.script \
           and '--merge: will just attempt the migration' in command.output


def get_new_command(command):
    return u'{} --merge'.format(command.script)



File: thefuck/rules/git_add_force.py

from thefuck.utils import replace_argument
from thefuck.specific.git import git_support


@git_support
def match(command):
    return ('add' in command.script_parts
            and 'Use -f if you really want to add them.' in command.output)


@git_support
def get_new_command(command):
    return replace_argument(command.script, 'add', 'add --force')



File: thefuck/rules/git_main_master.py

from thefuck.specific.git import git_support


@git_support
def match(command):
    return "'master'" in command.output or "'main'" in command.output


@git_support
def get_new_command(command):
    if "'master'" in command.output:
        return command.script.replace("master", "main")
    return command.script.replace("main", "master")


priority = 1200



File: thefuck/rules/no_such_file.py

import re
from thefuck.shells import shell


patterns = (
    r"mv: cannot move '[^']*' to '([^']*)': No such file or directory",
    r"mv: cannot move '[^']*' to '([^']*)': Not a directory",
    r"cp: cannot create regular file '([^']*)': No such file or directory",
    r"cp: cannot create regular file '([^']*)': Not a directory",
)


def match(command):
    for pattern in patterns:
        if re.search(pattern, command.output):
            return True

    return False


def get_new_command(command):
    for pattern in patterns:
        file = re.findall(pattern, command.output)

        if file:
            file = file[0]
            dir = file[0:file.rfind('/')]

            formatme = shell.and_('mkdir -p {}', '{}')
            return formatme.format(dir, command.script)



File: thefuck/rules/sed_unterminated_s.py

import shlex
from thefuck.shells import shell
from thefuck.utils import for_app


@for_app('sed')
def match(command):
    return "unterminated `s' command" in command.output


def get_new_command(command):
    script = shlex.split(command.script)

    for (i, e) in enumerate(script):
        if e.startswith(('s/', '-es/')) and e[-1] != '/':
            script[i] += '/'

    return ' '.join(map(shell.quote, script))



File: thefuck/rules/git_branch_delete_checked_out.py

from thefuck.shells import shell
from thefuck.specific.git import git_support
from thefuck.utils import replace_argument


@git_support
def match(command):
    return (
        ("branch -d" in command.script or "branch -D" in command.script)
        and "error: Cannot delete branch '" in command.output
        and "' checked out at '" in command.output
    )


@git_support
def get_new_command(command):
    return shell.and_("git checkout master", "{}").format(
        replace_argument(command.script, "-d", "-D")
    )



File: thefuck/rules/sudo.py

patterns = ['permission denied',
            'eacces',
            'pkg: insufficient privileges',
            'you cannot perform this operation unless you are root',
            'non-root users cannot',
            'operation not permitted',
            'not super-user',
            'superuser privilege',
            'root privilege',
            'this command has to be run under the root user.',
            'this operation requires root.',
            'requested operation requires superuser privilege',
            'must be run as root',
            'must run as root',
            'must be superuser',
            'must be root',
            'need to be root',
            'need root',
            'needs to be run as root',
            'only root can ',
            'you don\'t have access to the history db.',
            'authentication is required',
            'edspermissionerror',
            'you don\'t have write permissions',
            'use `sudo`',
            'sudorequirederror',
            'error: insufficient privileges',
            'updatedb: can not open a temporary file']


def match(command):
    if command.script_parts and '&&' not in command.script_parts and command.script_parts[0] == 'sudo':
        return False

    for pattern in patterns:
        if pattern in command.output.lower():
            return True
    return False


def get_new_command(command):
    if '&&' in command.script:
        return u'sudo sh -c "{}"'.format(" ".join([part for part in command.script_parts if part != "sudo"]))
    elif '>' in command.script:
        return u'sudo sh -c "{}"'.format(command.script.replace('"', '\\"'))
    else:
        return u'sudo {}'.format(command.script)



File: thefuck/rules/git_help_aliased.py

from thefuck.specific.git import git_support


@git_support
def match(command):
    return 'help' in command.script and ' is aliased to ' in command.output


@git_support
def get_new_command(command):
    aliased = command.output.split('`', 2)[2].split("'", 1)[0].split(' ', 1)[0]
    return 'git help {}'.format(aliased)



File: thefuck/rules/gradle_no_task.py

import re
from subprocess import Popen, PIPE
from thefuck.utils import for_app, eager, replace_command

regex = re.compile(r"Task '(.*)' (is ambiguous|not found)")


@for_app('gradle', 'gradlew')
def match(command):
    return regex.findall(command.output)


@eager
def _get_all_tasks(gradle):
    proc = Popen([gradle, 'tasks'], stdout=PIPE)
    should_yield = False
    for line in proc.stdout.readlines():
        line = line.decode().strip()
        if line.startswith('----'):
            should_yield = True
            continue

        if not line.strip():
            should_yield = False
            continue

        if should_yield and not line.startswith('All tasks runnable from root project'):
            yield line.split(' ')[0]


def get_new_command(command):
    wrong_task = regex.findall(command.output)[0][0]
    all_tasks = _get_all_tasks(command.script_parts[0])
    return replace_command(command, wrong_task, all_tasks)



File: thefuck/rules/mvn_unknown_lifecycle_phase.py

from thefuck.utils import for_app, get_close_matches, replace_command
import re


def _get_failed_lifecycle(command):
    return re.search(r'\[ERROR\] Unknown lifecycle phase "(.+)"',
                     command.output)


def _getavailable_lifecycles(command):
    return re.search(
        r'Available lifecycle phases are: (.+) -> \[Help 1\]', command.output)


@for_app('mvn')
def match(command):
    failed_lifecycle = _get_failed_lifecycle(command)
    available_lifecycles = _getavailable_lifecycles(command)
    return available_lifecycles and failed_lifecycle


def get_new_command(command):
    failed_lifecycle = _get_failed_lifecycle(command)
    available_lifecycles = _getavailable_lifecycles(command)
    if available_lifecycles and failed_lifecycle:
        selected_lifecycle = get_close_matches(
            failed_lifecycle.group(1), available_lifecycles.group(1).split(", "))
        return replace_command(command, failed_lifecycle.group(1), selected_lifecycle)
    else:
        return []



File: thefuck/rules/unknown_command.py

import re
from thefuck.utils import replace_command


def match(command):
    return (re.search(r"([^:]*): Unknown command.*", command.output) is not None
            and re.search(r"Did you mean ([^?]*)?", command.output) is not None)


def get_new_command(command):
    broken_cmd = re.findall(r"([^:]*): Unknown command.*", command.output)[0]
    matched = re.findall(r"Did you mean ([^?]*)?", command.output)
    return replace_command(command, broken_cmd, matched)



File: thefuck/rules/cd_correction.py

"""Attempts to spellcheck and correct failed cd commands"""

import os
import six
from thefuck.specific.sudo import sudo_support
from thefuck.rules import cd_mkdir
from thefuck.utils import for_app, get_close_matches

__author__ = "mmussomele"

MAX_ALLOWED_DIFF = 0.6


def _get_sub_dirs(parent):
    """Returns a list of the child directories of the given parent directory"""
    return [child for child in os.listdir(parent) if os.path.isdir(os.path.join(parent, child))]


@sudo_support
@for_app('cd')
def match(command):
    """Match function copied from cd_mkdir.py"""
    return (
        command.script.startswith('cd ') and any((
            'no such file or directory' in command.output.lower(),
            'cd: can\'t cd to' in command.output.lower(),
            'does not exist' in command.output.lower()
        )))


@sudo_support
def get_new_command(command):
    """
    Attempt to rebuild the path string by spellchecking the directories.
    If it fails (i.e. no directories are a close enough match), then it
    defaults to the rules of cd_mkdir.
    Change sensitivity by changing MAX_ALLOWED_DIFF. Default value is 0.6
    """
    dest = command.script_parts[1].split(os.sep)
    if dest[-1] == '':
        dest = dest[:-1]

    if dest[0] == '':
        cwd = os.sep
        dest = dest[1:]
    elif six.PY2:
        cwd = os.getcwdu()
    else:
        cwd = os.getcwd()
    for directory in dest:
        if directory == ".":
            continue
        elif directory == "..":
            cwd = os.path.split(cwd)[0]
            continue
        best_matches = get_close_matches(directory, _get_sub_dirs(cwd), cutoff=MAX_ALLOWED_DIFF)
        if best_matches:
            cwd = os.path.join(cwd, best_matches[0])
        else:
            return cd_mkdir.get_new_command(command)
    return u'cd "{0}"'.format(cwd)



File: thefuck/rules/man.py

from thefuck.utils import for_app


@for_app('man', at_least=1)
def match(command):
    return True


def get_new_command(command):
    if '3' in command.script:
        return command.script.replace("3", "2")
    if '2' in command.script:
        return command.script.replace("2", "3")

    last_arg = command.script_parts[-1]
    help_command = last_arg + ' --help'

    # If there are no man pages for last_arg, suggest `last_arg --help` instead.
    # Otherwise, suggest `--help` after suggesting other man page sections.
    if command.output.strip() == 'No manual entry for ' + last_arg:
        return [help_command]

    split_cmd2 = command.script_parts
    split_cmd3 = split_cmd2[:]

    split_cmd2.insert(1, ' 2 ')
    split_cmd3.insert(1, ' 3 ')

    return [
        "".join(split_cmd3),
        "".join(split_cmd2),
        help_command,
    ]



File: thefuck/rules/dirty_untar.py

import tarfile
import os
from thefuck.utils import for_app
from thefuck.shells import shell


tar_extensions = ('.tar', '.tar.Z', '.tar.bz2', '.tar.gz', '.tar.lz',
                  '.tar.lzma', '.tar.xz', '.taz', '.tb2', '.tbz', '.tbz2',
                  '.tgz', '.tlz', '.txz', '.tz')


def _is_tar_extract(cmd):
    if '--extract' in cmd:
        return True

    cmd = cmd.split()

    return len(cmd) > 1 and 'x' in cmd[1]


def _tar_file(cmd):
    for c in cmd:
        for ext in tar_extensions:
            if c.endswith(ext):
                return (c, c[0:len(c) - len(ext)])


@for_app('tar')
def match(command):
    return ('-C' not in command.script
            and _is_tar_extract(command.script)
            and _tar_file(command.script_parts) is not None)


def get_new_command(command):
    dir = shell.quote(_tar_file(command.script_parts)[1])
    return shell.and_('mkdir -p {dir}', '{cmd} -C {dir}') \
        .format(dir=dir, cmd=command.script)


def side_effect(old_cmd, command):
    with tarfile.TarFile(_tar_file(old_cmd.script_parts)[0]) as archive:
        for file in archive.getnames():
            if not os.path.abspath(file).startswith(os.getcwd()):
                # it's unsafe to overwrite files outside of the current directory
                continue

            try:
                os.remove(file)
            except OSError:
                # does not try to remove directories as we cannot know if they
                # already existed before
                pass



File: thefuck/rules/mvn_no_command.py

from thefuck.utils import for_app


@for_app('mvn')
def match(command):
    return 'No goals have been specified for this build' in command.output


def get_new_command(command):
    return [command.script + ' clean package',
            command.script + ' clean install']



File: thefuck/rules/mkdir_p.py

import re
from thefuck.specific.sudo import sudo_support


@sudo_support
def match(command):
    return ('mkdir' in command.script
            and 'No such file or directory' in command.output)


@sudo_support
def get_new_command(command):
    return re.sub('\\bmkdir (.*)', 'mkdir -p \\1', command.script)



File: thefuck/rules/cargo.py

def match(command):
    return command.script == 'cargo'


def get_new_command(command):
    return 'cargo build'



File: thefuck/rules/switch_lang.py

# -*- encoding: utf-8 -*-
from thefuck.utils import memoize, get_alias

target_layout = '''qwertyuiop[]asdfghjkl;'zxcvbnm,./QWERTYUIOP{}ASDFGHJKL:"ZXCVBNM<>?'''
# any new keyboard layout must be appended

greek = u''';ςερτυθιοπ[]ασδφγηξκλ΄ζχψωβνμ,./:΅ΕΡΤΥΘΙΟΠ{}ΑΣΔΦΓΗΞΚΛ¨"ΖΧΨΩΒΝΜ<>?'''
korean = u'''ㅂㅈㄷㄱㅅㅛㅕㅑㅐㅔ[]ㅁㄴㅇㄹㅎㅗㅓㅏㅣ;'ㅋㅌㅊㅍㅠㅜㅡ,./ㅃㅉㄸㄲㅆㅛㅕㅑㅒㅖ{}ㅁㄴㅇㄹㅎㅗㅓㅏㅣ:"ㅋㅌㅊㅍㅠㅜㅡ<>?'''

source_layouts = [u'''йцукенгшщзхъфывапролджэячсмитьбю.ЙЦУКЕНГШЩЗХЪФЫВАПРОЛДЖЭЯЧСМИТЬБЮ,''',
                  u'''йцукенгшщзхїфівапролджєячсмитьбю.ЙЦУКЕНГШЩЗХЇФІВАПРОЛДЖЄЯЧСМИТЬБЮ,''',
                  u'''ضصثقفغعهخحجچشسیبلاتنمکگظطزرذدپو./ًٌٍَُِّْ][}{ؤئيإأآة»«:؛كٓژٰ‌ٔء><؟''',
                  u'''/'קראטוןםפ][שדגכעיחלךף,זסבהנמצתץ.QWERTYUIOP{}ASDFGHJKL:"ZXCVBNM<>?''',
                  greek,
                  korean]

source_to_target = {
    greek: {u';': "q", u'ς': "w", u'ε': "e", u'ρ': "r", u'τ': "t", u'υ': "y",
            u'θ': "u", u'ι': "i", u'ο': "o", u'π': "p", u'[': "[", u']': "]",
            u'α': "a", u'σ': "s", u'δ': "d", u'φ': "f", u'γ': "g", u'η': "h",
            u'ξ': "j", u'κ': "k", u'λ': "l", u'΄': "'", u'ζ': "z", u'χ': "x",
            u'ψ': "c", u'ω': "v", u'β': "b", u'ν': "n", u'μ': "m", u',': ",",
            u'.': ".", u'/': "/", u':': "Q", u'΅': "W", u'Ε': "E", u'Ρ': "R",
            u'Τ': "T", u'Υ': "Y", u'Θ': "U", u'Ι': "I", u'Ο': "O", u'Π': "P",
            u'{': "{", u'}': "}", u'Α': "A", u'Σ': "S", u'Δ': "D", u'Φ': "F",
            u'Γ': "G", u'Η': "H", u'Ξ': "J", u'Κ': "K", u'Λ': "L", u'¨': ":",
            u'"': '"', u'Ζ': "Z", u'Χ': "X", u'Ψ': "C", u'Ω': "V", u'Β': "B",
            u'Ν': "N", u'Μ': "M", u'<': "<", u'>': ">", u'?': "?", u'ά': "a",
            u'έ': "e", u'ύ': "y", u'ί': "i", u'ό': "o", u'ή': 'h', u'ώ': u"v",
            u'Ά': "A", u'Έ': "E", u'Ύ': "Y", u'Ί': "I", u'Ό': "O", u'Ή': "H",
            u'Ώ': "V"},
}

'''Lists used for decomposing korean letters.'''
HEAD_LIST = [u'ㄱ', u'ㄲ', u'ㄴ', u'ㄷ', u'ㄸ', u'ㄹ', u'ㅁ', u'ㅂ', u'ㅃ', u'ㅅ', u'ㅆ',
             u'ㅇ', u'ㅈ', u'ㅉ', u'ㅊ', u'ㅋ', u'ㅌ', u'ㅍ', u'ㅎ']
BODY_LIST = [u'ㅏ', u'ㅐ', u'ㅑ', u'ㅒ', u'ㅓ', u'ㅔ', u'ㅕ', u'ㅖ', u'ㅗ', u'ㅘ', u'ㅙ',
             u'ㅚ', u'ㅛ', u'ㅜ', u'ㅝ', u'ㅞ', u'ㅟ', u'ㅠ', u'ㅡ', u'ㅢ', u'ㅣ']
TAIL_LIST = [u' ', u'ㄱ', u'ㄲ', u'ㄳ', u'ㄴ', u'ㄵ', u'ㄶ', u'ㄷ', u'ㄹ', u'ㄺ', u'ㄻ',
             u'ㄼ', u'ㄽ', u'ㄾ', u'ㄿ', u'ㅀ', u'ㅁ', u'ㅂ', u'ㅄ', u'ㅅ', u'ㅆ', u'ㅇ', u'ㅈ',
             u'ㅊ', u'ㅋ', u'ㅌ', u'ㅍ', u'ㅎ']
DOUBLE_LIST = [u'ㅘ', u'ㅙ', u'ㅚ', u'ㅝ', u'ㅞ', u'ㅟ', u'ㅢ', u'ㄳ', u'ㄵ', u'ㄶ', u'ㄺ',
               u'ㄻ', u'ㄼ', u'ㄽ', u'ㄾ', u'ㅀ', u'ㅄ']
DOUBLE_MOD_LIST = [u'ㅗㅏ', u'ㅗㅐ', u'ㅗㅣ', u'ㅜㅓ', u'ㅜㅔ', u'ㅜㅣ', u'ㅡㅣ', u'ㄱㅅ',
                   u'ㄴㅈ', u'ㄴㅎ', u'ㄹㄱ', u'ㄹㅁ', u'ㄹㅂ', u'ㄹㅅ', u'ㄹㅌ', u'ㄹㅎ', u'ㅂㅅ']


@memoize
def _get_matched_layout(command):
    # don't use command.split_script here because a layout mismatch will likely
    # result in a non-splitable script as per shlex
    cmd = command.script.split(' ')
    for source_layout in source_layouts:
        is_all_match = True
        for cmd_part in cmd:
            if not all([ch in source_layout or ch in '-_' for ch in cmd_part]):
                is_all_match = False
                break

        if is_all_match:
            return source_layout


def _switch(ch, layout):
    if ch in layout:
        return target_layout[layout.index(ch)]
    return ch


def _switch_command(command, layout):
    # Layouts with different amount of characters than English
    if layout in source_to_target:
        return ''.join(source_to_target[layout].get(ch, ch)
                       for ch in command.script)

    return ''.join(_switch(ch, layout) for ch in command.script)


def _decompose_korean(command):
    def _change_double(ch):
        if ch in DOUBLE_LIST:
            return DOUBLE_MOD_LIST[DOUBLE_LIST.index(ch)]
        return ch

    hg_str = u''
    for ch in command.script:
        if u'가' <= ch <= u'힣':
            ord_ch = ord(ch) - ord(u'가')
            hd = ord_ch // 588
            bd = (ord_ch - 588 * hd) // 28
            tl = ord_ch - 588 * hd - 28 * bd
            for ch in [HEAD_LIST[hd], BODY_LIST[bd], TAIL_LIST[tl]]:
                if ch != ' ':
                    hg_str += _change_double(ch)
        else:
            hg_str += _change_double(ch)
    return hg_str


def match(command):
    if 'not found' not in command.output:
        return False
    if any(u'ㄱ' <= ch <= u'ㅎ' or u'ㅏ' <= ch <= u'ㅣ' or u'가' <= ch <= u'힣'
            for ch in command.script):
        return True

    matched_layout = _get_matched_layout(command)
    return (matched_layout and
            _switch_command(command, matched_layout) != get_alias())


def get_new_command(command):
    if any(u'ㄱ' <= ch <= u'ㅎ' or u'ㅏ' <= ch <= u'ㅣ' or u'가' <= ch <= u'힣'
            for ch in command.script):
        command.script = _decompose_korean(command)
    matched_layout = _get_matched_layout(command)
    return _switch_command(command, matched_layout)



File: thefuck/rules/brew_link.py

from thefuck.utils import for_app


@for_app('brew', at_least=2)
def match(command):
    return (command.script_parts[1] in ['ln', 'link']
            and "brew link --overwrite --dry-run" in command.output)


def get_new_command(command):
    command_parts = command.script_parts[:]
    command_parts[1] = 'link'
    command_parts.insert(2, '--overwrite')
    command_parts.insert(3, '--dry-run')
    return ' '.join(command_parts)



File: thefuck/rules/fab_command_not_found.py

from thefuck.utils import eager, get_closest, for_app


@for_app('fab')
def match(command):
    return 'Warning: Command(s) not found:' in command.output


# We need different behavior then in get_all_matched_commands.
@eager
def _get_between(content, start, end=None):
    should_yield = False
    for line in content.split('\n'):
        if start in line:
            should_yield = True
            continue

        if end and end in line:
            return

        if should_yield and line:
            yield line.strip().split(' ')[0]


def get_new_command(command):
    not_found_commands = _get_between(
        command.output, 'Warning: Command(s) not found:',
        'Available commands:')
    possible_commands = _get_between(
        command.output, 'Available commands:')

    script = command.script
    for not_found in not_found_commands:
        fix = get_closest(not_found, possible_commands)
        script = script.replace(' {}'.format(not_found),
                                ' {}'.format(fix))

    return script



File: thefuck/rules/vagrant_up.py

from thefuck.shells import shell
from thefuck.utils import for_app


@for_app('vagrant')
def match(command):
    return 'run `vagrant up`' in command.output.lower()


def get_new_command(command):
    cmds = command.script_parts
    machine = None
    if len(cmds) >= 3:
        machine = cmds[2]

    start_all_instances = shell.and_(u"vagrant up", command.script)
    if machine is None:
        return start_all_instances
    else:
        return [shell.and_(u"vagrant up {}".format(machine), command.script),
                start_all_instances]



File: thefuck/rules/omnienv_no_such_command.py

import re
from thefuck.utils import (cache, for_app, replace_argument, replace_command,
                           which)
from subprocess import PIPE, Popen


supported_apps = 'goenv', 'nodenv', 'pyenv', 'rbenv'
enabled_by_default = any(which(a) for a in supported_apps)


COMMON_TYPOS = {
    'list': ['versions', 'install --list'],
    'remove': ['uninstall'],
}


@for_app(*supported_apps, at_least=1)
def match(command):
    return 'env: no such command ' in command.output


def get_app_commands(app):
    proc = Popen([app, 'commands'], stdout=PIPE)
    return [line.decode('utf-8').strip() for line in proc.stdout.readlines()]


def get_new_command(command):
    broken = re.findall(r"env: no such command ['`]([^']*)'", command.output)[0]
    matched = [replace_argument(command.script, broken, common_typo)
               for common_typo in COMMON_TYPOS.get(broken, [])]

    app = command.script_parts[0]
    app_commands = cache(which(app))(get_app_commands)(app)
    matched.extend(replace_command(command, broken, app_commands))
    return matched



File: thefuck/rules/git_rm_recursive.py

from thefuck.specific.git import git_support


@git_support
def match(command):
    return (' rm ' in command.script
            and "fatal: not removing '" in command.output
            and "' recursively without -r" in command.output)


@git_support
def get_new_command(command):
    command_parts = command.script_parts[:]
    index = command_parts.index('rm') + 1
    command_parts.insert(index, '-r')
    return u' '.join(command_parts)



File: thefuck/rules/tmux.py

import re
from thefuck.utils import replace_command, for_app


@for_app('tmux')
def match(command):
    return ('ambiguous command:' in command.output
            and 'could be:' in command.output)


def get_new_command(command):
    cmd = re.match(r"ambiguous command: (.*), could be: (.*)",
                   command.output)

    old_cmd = cmd.group(1)
    suggestions = [c.strip() for c in cmd.group(2).split(',')]

    return replace_command(command, old_cmd, suggestions)



File: thefuck/rules/pip_install.py

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


@sudo_support
@for_app('pip')
def match(command):
    return ('pip install' in command.script and 'Permission denied' in command.output)


def get_new_command(command):
    if '--user' not in command.script:  # add --user (attempt 1)
        return command.script.replace(' install ', ' install --user ')

    return 'sudo {}'.format(command.script.replace(' --user', ''))  # since --user didn't fix things, let's try sudo (attempt 2)



File: thefuck/rules/ifconfig_device_not_found.py

import subprocess
from thefuck.utils import for_app, replace_command, eager


@for_app('ifconfig')
def match(command):
    return 'error fetching interface information: Device not found' \
           in command.output


@eager
def _get_possible_interfaces():
    proc = subprocess.Popen(['ifconfig', '-a'], stdout=subprocess.PIPE)
    for line in proc.stdout.readlines():
        line = line.decode()
        if line and line != '\n' and not line.startswith(' '):
            yield line.split(' ')[0]


def get_new_command(command):
    interface = command.output.split(' ')[0][:-1]
    possible_interfaces = _get_possible_interfaces()
    return replace_command(command, interface, possible_interfaces)



File: thefuck/rules/chmod_x.py

import os
from thefuck.shells import shell


def match(command):
    return (command.script.startswith('./')
            and 'permission denied' in command.output.lower()
            and os.path.exists(command.script_parts[0])
            and not os.access(command.script_parts[0], os.X_OK))


def get_new_command(command):
    return shell.and_(
        'chmod +x {}'.format(command.script_parts[0][2:]),
        command.script)



File: thefuck/rules/rm_dir.py

import re
from thefuck.specific.sudo import sudo_support


@sudo_support
def match(command):
    return ('rm' in command.script
            and 'is a directory' in command.output.lower())


@sudo_support
def get_new_command(command):
    arguments = '-rf'
    if 'hdfs' in command.script:
        arguments = '-r'
    return re.sub('\\brm (.*)', 'rm ' + arguments + ' \\1', command.script)



File: thefuck/rules/cat_dir.py

import os
from thefuck.utils import for_app


@for_app('cat', at_least=1)
def match(command):
    return (
        command.output.startswith('cat: ') and
        os.path.isdir(command.script_parts[1])
    )


def get_new_command(command):
    return command.script.replace('cat', 'ls', 1)



File: thefuck/rules/php_s.py

from thefuck.utils import replace_argument, for_app


@for_app('php', at_least=2)
def match(command):
    return ('-s' in command.script_parts
            and command.script_parts[-1] != '-s')


def get_new_command(command):
    return replace_argument(command.script, "-s", "-S")



File: thefuck/rules/__init__.py




File: thefuck/rules/react_native_command_unrecognized.py

import re
from subprocess import Popen, PIPE
from thefuck.utils import for_app, replace_command, cache, eager


@for_app('react-native')
def match(command):
    return re.findall(r"Unrecognized command '.*'", command.output)


@cache('package.json')
@eager
def _get_commands():
    proc = Popen(['react-native', '--help'], stdout=PIPE)
    should_yield = False
    for line in proc.stdout.readlines():
        line = line.decode().strip()

        if not line:
            continue

        if 'Commands:' in line:
            should_yield = True
            continue

        if should_yield:
            yield line.split(' ')[0]


def get_new_command(command):
    misspelled_command = re.findall(r"Unrecognized command '(.*)'",
                                    command.output)[0]
    commands = _get_commands()
    return replace_command(command, misspelled_command, commands)



File: thefuck/rules/git_clone_git_clone.py

from thefuck.specific.git import git_support


@git_support
def match(command):
    return (' git clone ' in command.script
            and 'fatal: Too many arguments.' in command.output)


@git_support
def get_new_command(command):
    return command.script.replace(' git clone ', ' ', 1)



File: thefuck/rules/test.py.py

def match(command):
    return command.script == 'test.py' and 'not found' in command.output


def get_new_command(command):
    return 'pytest'


# make it come before the python_command rule
priority = 900



File: thefuck/rules/git_merge.py

import re
from thefuck.utils import replace_argument
from thefuck.specific.git import git_support


@git_support
def match(command):
    return ('merge' in command.script
            and ' - not something we can merge' in command.output
            and 'Did you mean this?' in command.output)


@git_support
def get_new_command(command):
    unknown_branch = re.findall(r'merge: (.+) - not something we can merge', command.output)[0]
    remote_branch = re.findall(r'Did you mean this\?\n\t([^\n]+)', command.output)[0]

    return replace_argument(command.script, unknown_branch, remote_branch)



File: thefuck/rules/git_rebase_no_changes.py

from thefuck.specific.git import git_support


@git_support
def match(command):
    return (
        {'rebase', '--continue'}.issubset(command.script_parts) and
        'No changes - did you forget to use \'git add\'?' in command.output
    )


def get_new_command(command):
    return 'git rebase --skip'



File: thefuck/rules/git_checkout.py

import re
import subprocess
from thefuck import utils
from thefuck.utils import replace_argument
from thefuck.specific.git import git_support
from thefuck.shells import shell


@git_support
def match(command):
    return ('did not match any file(s) known to git' in command.output
            and "Did you forget to 'git add'?" not in command.output)


def get_branches():
    proc = subprocess.Popen(
        ['git', 'branch', '-a', '--no-color', '--no-column'],
        stdout=subprocess.PIPE)
    for line in proc.stdout.readlines():
        line = line.decode('utf-8')
        if '->' in line:    # Remote HEAD like b'  remotes/origin/HEAD -> origin/master'
            continue
        if line.startswith('*'):
            line = line.split(' ')[1]
        if line.strip().startswith('remotes/'):
            line = '/'.join(line.split('/')[2:])
        yield line.strip()


@git_support
def get_new_command(command):
    missing_file = re.findall(
        r"error: pathspec '([^']*)' "
        r"did not match any file\(s\) known to git", command.output)[0]
    closest_branch = utils.get_closest(missing_file, get_branches(),
                                       fallback_to_first=False)

    new_commands = []

    if closest_branch:
        new_commands.append(replace_argument(command.script, missing_file, closest_branch))
    if command.script_parts[1] == 'checkout':
        new_commands.append(replace_argument(command.script, 'checkout', 'checkout -b'))

    if not new_commands:
        new_commands.append(shell.and_('git branch {}', '{}').format(
            missing_file, command.script))

    return new_commands



File: thefuck/rules/npm_run_script.py

from thefuck.specific.npm import npm_available, get_scripts
from thefuck.utils import for_app

enabled_by_default = npm_available


@for_app('npm')
def match(command):
    return ('Usage: npm <command>' in command.output
            and not any(part.startswith('ru') for part in command.script_parts)
            and command.script_parts[1] in get_scripts())


def get_new_command(command):
    parts = command.script_parts[:]
    parts.insert(1, 'run-script')
    return ' '.join(parts)



File: thefuck/rules/go_run.py

from thefuck.utils import for_app
# Appends .go when compiling go files
#
# Example:
# > go run foo
# error: go run: no go files listed


@for_app('go')
def match(command):
    return (command.script.startswith('go run ')
            and not command.script.endswith('.go'))


def get_new_command(command):
    return command.script + '.go'



File: thefuck/rules/git_push_force.py

from thefuck.utils import replace_argument
from thefuck.specific.git import git_support


@git_support
def match(command):
    return ('push' in command.script
            and '! [rejected]' in command.output
            and 'failed to push some refs to' in command.output
            and 'Updates were rejected because the tip of your current branch is behind' in command.output)


@git_support
def get_new_command(command):
    return replace_argument(command.script, 'push', 'push --force-with-lease')


enabled_by_default = False



File: thefuck/rules/git_push_without_commits.py

import re
from thefuck.shells import shell
from thefuck.specific.git import git_support


@git_support
def match(command):
    return bool(re.search(r"src refspec \w+ does not match any", command.output))


def get_new_command(command):
    return shell.and_('git commit -m "Initial commit"', command.script)



File: thefuck/rules/docker_login.py

from thefuck.utils import for_app
from thefuck.shells import shell


@for_app('docker')
def match(command):
    return ('docker' in command.script
            and "access denied" in command.output
            and "may require 'docker login'" in command.output)


def get_new_command(command):
    return shell.and_('docker login', command.script)



File: thefuck/rules/ln_no_hard_link.py

# -*- coding: utf-8 -*-
"""Suggest creating symbolic link if hard link is not allowed.

Example:
> ln barDir barLink
ln: ‘barDir’: hard link not allowed for directory

--> ln -s barDir barLink
"""

import re
from thefuck.specific.sudo import sudo_support


@sudo_support
def match(command):
    return (command.output.endswith("hard link not allowed for directory") and
            command.script_parts[0] == 'ln')


@sudo_support
def get_new_command(command):
    return re.sub(r'^ln ', 'ln -s ', command.script)



File: thefuck/rules/mercurial.py

import re
from thefuck.utils import get_closest, for_app


def extract_possibilities(command):
    possib = re.findall(r'\n\(did you mean one of ([^\?]+)\?\)', command.output)
    if possib:
        return possib[0].split(', ')
    possib = re.findall(r'\n    ([^$]+)$', command.output)
    if possib:
        return possib[0].split(' ')
    return possib


@for_app('hg')
def match(command):
    return ('hg: unknown command' in command.output
            and '(did you mean one of ' in command.output
            or "hg: command '" in command.output
            and "' is ambiguous:" in command.output)


def get_new_command(command):
    script = command.script_parts[:]
    possibilities = extract_possibilities(command)
    script[1] = get_closest(script[1], possibilities)
    return ' '.join(script)



File: thefuck/rules/python_module_error.py

import re
from thefuck.shells import shell

MISSING_MODULE = r"ModuleNotFoundError: No module named '([^']+)'"


def match(command):
    return "ModuleNotFoundError: No module named '" in command.output


def get_new_command(command):
    missing_module = re.findall(MISSING_MODULE, command.output)[0]
    return shell.and_("pip install {}".format(missing_module), command.script)



File: thefuck/rules/docker_image_being_used_by_container.py

from thefuck.utils import for_app
from thefuck.shells import shell


@for_app('docker')
def match(command):
    '''
    Matches a command's output with docker's output
    warning you that you need to remove a container before removing an image.
    '''
    return 'image is being used by running container' in command.output


def get_new_command(command):
    '''
    Prepends docker container rm -f {container ID} to
    the previous docker image rm {image ID} command
    '''
    container_id = command.output.strip().split(' ')
    return shell.and_('docker container rm -f {}', '{}').format(container_id[-1], command.script)



File: thefuck/rules/git_push_different_branch_names.py

import re
from thefuck.specific.git import git_support


@git_support
def match(command):
    return "push" in command.script and "The upstream branch of your current branch does not match" in command.output


@git_support
def get_new_command(command):
    return re.findall(r'^ +(git push [^\s]+ [^\s]+)', command.output, re.MULTILINE)[0]



File: thefuck/rules/apt_invalid_operation.py

import subprocess
from thefuck.specific.apt import apt_available
from thefuck.specific.sudo import sudo_support
from thefuck.utils import for_app, eager, replace_command

enabled_by_default = apt_available


@sudo_support
@for_app('apt', 'apt-get', 'apt-cache')
def match(command):
    return 'E: Invalid operation' in command.output


@eager
def _parse_apt_operations(help_text_lines):
    is_commands_list = False
    for line in help_text_lines:
        line = line.decode().strip()
        if is_commands_list and line:
            yield line.split()[0]
        elif line.startswith('Basic commands:') \
                or line.startswith('Most used commands:'):
            is_commands_list = True


@eager
def _parse_apt_get_and_cache_operations(help_text_lines):
    is_commands_list = False
    for line in help_text_lines:
        line = line.decode().strip()
        if is_commands_list:
            if not line:
                return

            yield line.split()[0]
        elif line.startswith('Commands:') \
                or line.startswith('Most used commands:'):
            is_commands_list = True


def _get_operations(app):
    proc = subprocess.Popen([app, '--help'],
                            stdout=subprocess.PIPE,
                            stderr=subprocess.PIPE)
    lines = proc.stdout.readlines()

    if app == 'apt':
        return _parse_apt_operations(lines)
    else:
        return _parse_apt_get_and_cache_operations(lines)


@sudo_support
def get_new_command(command):
    invalid_operation = command.output.split()[-1]

    if invalid_operation == 'uninstall':
        return [command.script.replace('uninstall', 'remove')]

    else:
        operations = _get_operations(command.script_parts[0])
        return replace_command(command, invalid_operation, operations)



File: thefuck/rules/git_fix_stash.py

from thefuck import utils
from thefuck.utils import replace_argument
from thefuck.specific.git import git_support


@git_support
def match(command):
    if command.script_parts and len(command.script_parts) > 1:
        return (command.script_parts[1] == 'stash'
                and 'usage:' in command.output)
    else:
        return False


# git's output here is too complicated to be parsed (see the test file)
stash_commands = (
    'apply',
    'branch',
    'clear',
    'drop',
    'list',
    'pop',
    'save',
    'show')


@git_support
def get_new_command(command):
    stash_cmd = command.script_parts[2]
    fixed = utils.get_closest(stash_cmd, stash_commands, fallback_to_first=False)

    if fixed is not None:
        return replace_argument(command.script, stash_cmd, fixed)
    else:
        cmd = command.script_parts[:]
        cmd.insert(2, 'save')
        return ' '.join(cmd)



File: thefuck/rules/workon_doesnt_exists.py

from thefuck.utils import for_app, replace_command, eager, memoize
from thefuck.system import Path


@memoize
@eager
def _get_all_environments():
    root = Path('~/.virtualenvs').expanduser()
    if not root.is_dir():
        return

    for child in root.iterdir():
        if child.is_dir():
            yield child.name


@for_app('workon')
def match(command):
    return (len(command.script_parts) >= 2
            and command.script_parts[1] not in _get_all_environments())


def get_new_command(command):
    misspelled_env = command.script_parts[1]
    create_new = u'mkvirtualenv {}'.format(misspelled_env)

    available = _get_all_environments()
    if available:
        return (replace_command(command, misspelled_env, available)
                + [create_new])
    else:
        return create_new



File: thefuck/rules/git_rm_staged.py

from thefuck.specific.git import git_support


@git_support
def match(command):
    return (' rm ' in command.script and
            'error: the following file has changes staged in the index' in command.output and
            'use --cached to keep the file, or -f to force removal' in command.output)


@git_support
def get_new_command(command):
    command_parts = command.script_parts[:]
    index = command_parts.index('rm') + 1
    command_parts.insert(index, '--cached')
    command_list = [u' '.join(command_parts)]
    command_parts[index] = '-f'
    command_list.append(u' '.join(command_parts))
    return command_list



File: thefuck/rules/git_stash_pop.py

from thefuck.shells import shell
from thefuck.specific.git import git_support


@git_support
def match(command):
    return ('stash' in command.script
            and 'pop' in command.script
            and 'Your local changes to the following files would be overwritten by merge' in command.output)


@git_support
def get_new_command(command):
    return shell.and_('git add --update', 'git stash pop', 'git reset .')


# make it come before the other applicable rules
priority = 900



File: thefuck/rules/docker_not_command.py

from itertools import dropwhile, takewhile, islice
import re
import subprocess
from thefuck.utils import replace_command, for_app, which, cache
from thefuck.specific.sudo import sudo_support


@sudo_support
@for_app('docker')
def match(command):
    return 'is not a docker command' in command.output or 'Usage:	docker' in command.output


def _parse_commands(lines, starts_with):
    lines = dropwhile(lambda line: not line.startswith(starts_with), lines)
    lines = islice(lines, 1, None)
    lines = list(takewhile(lambda line: line.strip(), lines))
    return [line.strip().split(' ')[0] for line in lines]


def get_docker_commands():
    proc = subprocess.Popen('docker', stdout=subprocess.PIPE, stderr=subprocess.PIPE)

    # Old version docker returns its output to stdout, while newer version returns to stderr.
    lines = proc.stdout.readlines() or proc.stderr.readlines()
    lines = [line.decode('utf-8') for line in lines]

    # Only newer versions of docker have management commands in the help text.
    if 'Management Commands:\n' in lines:
        management_commands = _parse_commands(lines, 'Management Commands:')
    else:
        management_commands = []
    regular_commands = _parse_commands(lines, 'Commands:')
    return management_commands + regular_commands


if which('docker'):
    get_docker_commands = cache(which('docker'))(get_docker_commands)


@sudo_support
def get_new_command(command):
    if 'Usage:' in command.output and len(command.script_parts) > 1:
        management_subcommands = _parse_commands(command.output.split('\n'), 'Commands:')
        return replace_command(command, command.script_parts[2], management_subcommands)

    wrong_command = re.findall(
        r"docker: '(\w+)' is not a docker command.", command.output)[0]
    return replace_command(command, wrong_command, get_docker_commands())



File: thefuck/rules/dirty_unzip.py

import os
import zipfile
from thefuck.utils import for_app
from thefuck.shells import shell


def _is_bad_zip(file):
    try:
        with zipfile.ZipFile(file, 'r') as archive:
            return len(archive.namelist()) > 1
    except Exception:
        return False


def _zip_file(command):
    # unzip works that way:
    # unzip [-flags] file[.zip] [file(s) ...] [-x file(s) ...]
    #                ^          ^ files to unzip from the archive
    #                archive to unzip
    for c in command.script_parts[1:]:
        if not c.startswith('-'):
            if c.endswith('.zip'):
                return c
            else:
                return u'{}.zip'.format(c)


@for_app('unzip')
def match(command):
    if '-d' in command.script:
        return False

    zip_file = _zip_file(command)
    if zip_file:
        return _is_bad_zip(zip_file)
    else:
        return False


def get_new_command(command):
    return u'{} -d {}'.format(
        command.script, shell.quote(_zip_file(command)[:-4]))


def side_effect(old_cmd, command):
    with zipfile.ZipFile(_zip_file(old_cmd), 'r') as archive:
        for file in archive.namelist():
            if not os.path.abspath(file).startswith(os.getcwd()):
                # it's unsafe to overwrite files outside of the current directory
                continue

            try:
                os.remove(file)
            except OSError:
                # does not try to remove directories as we cannot know if they
                # already existed before
                pass


requires_output = False



File: thefuck/rules/gradle_wrapper.py

import os
from thefuck.utils import for_app, which


@for_app('gradle')
def match(command):
    return (not which(command.script_parts[0])
            and 'not found' in command.output
            and os.path.isfile('gradlew'))


def get_new_command(command):
    return u'./gradlew {}'.format(' '.join(command.script_parts[1:]))



File: thefuck/rules/git_branch_0flag.py

from thefuck.shells import shell
from thefuck.specific.git import git_support
from thefuck.utils import memoize


@memoize
def first_0flag(script_parts):
    return next((p for p in script_parts if len(p) == 2 and p.startswith("0")), None)


@git_support
def match(command):
    return command.script_parts[1] == "branch" and first_0flag(command.script_parts)


@git_support
def get_new_command(command):
    branch_name = first_0flag(command.script_parts)
    fixed_flag = branch_name.replace("0", "-")
    fixed_script = command.script.replace(branch_name, fixed_flag)
    if "A branch named '" in command.output and "' already exists." in command.output:
        delete_branch = u"git branch -D {}".format(branch_name)
        return shell.and_(delete_branch, fixed_script)
    return fixed_script



File: thefuck/rules/nixos_cmd_not_found.py

import re
from thefuck.specific.nix import nix_available
from thefuck.shells import shell

regex = re.compile(r'nix-env -iA ([^\s]*)')
enabled_by_default = nix_available


def match(command):
    return regex.findall(command.output)


def get_new_command(command):
    name = regex.findall(command.output)[0]
    return shell.and_('nix-env -iA {}'.format(name), command.script)



File: thefuck/rules/tsuru_not_command.py

import re
from thefuck.utils import get_all_matched_commands, replace_command, for_app


@for_app('tsuru')
def match(command):
    return (' is not a tsuru command. See "tsuru help".' in command.output
            and '\nDid you mean?\n\t' in command.output)


def get_new_command(command):
    broken_cmd = re.findall(r'tsuru: "([^"]*)" is not a tsuru command',
                            command.output)[0]
    return replace_command(command, broken_cmd,
                           get_all_matched_commands(command.output))



File: thefuck/rules/cp_omitting_directory.py

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


@sudo_support
@for_app('cp')
def match(command):
    output = command.output.lower()
    return 'omitting directory' in output or 'is a directory' in output


@sudo_support
def get_new_command(command):
    return re.sub(r'^cp', 'cp -a', command.script)



File: thefuck/rules/hostscli.py

import re
from thefuck.specific.sudo import sudo_support
from thefuck.utils import replace_command, for_app

no_command = "Error: No such command"
no_website = "hostscli.errors.WebsiteImportError"


@sudo_support
@for_app('hostscli')
def match(command):
    errors = [no_command, no_website]
    for error in errors:
        if error in command.output:
            return True
    return False


@sudo_support
def get_new_command(command):
    if no_website in command.output:
        return ['hostscli websites']

    misspelled_command = re.findall(
        r'Error: No such command ".*"', command.output)[0]
    commands = ['block', 'unblock', 'websites', 'block_all', 'unblock_all']
    return replace_command(command, misspelled_command, commands)



File: thefuck/rules/tsuru_login.py

from thefuck.shells import shell
from thefuck.utils import for_app


@for_app('tsuru')
def match(command):
    return ('not authenticated' in command.output
            and 'session has expired' in command.output)


def get_new_command(command):
    return shell.and_('tsuru login', command.script)



File: thefuck/rules/git_commit_reset.py

from thefuck.specific.git import git_support


@git_support
def match(command):
    return ('commit' in command.script_parts)


@git_support
def get_new_command(command):
    return 'git reset HEAD~'



File: thefuck/rules/git_add.py

import re
from thefuck.shells import shell
from thefuck.specific.git import git_support
from thefuck.system import Path
from thefuck.utils import memoize


@memoize
def _get_missing_file(command):
    pathspec = re.findall(
        r"error: pathspec '([^']*)' "
        r'did not match any file\(s\) known to git.', command.output)[0]
    if Path(pathspec).exists():
        return pathspec


@git_support
def match(command):
    return ('did not match any file(s) known to git.' in command.output
            and _get_missing_file(command))


@git_support
def get_new_command(command):
    missing_file = _get_missing_file(command)
    formatme = shell.and_('git add -- {}', '{}')
    return formatme.format(missing_file, command.script)



File: thefuck/rules/git_two_dashes.py

from thefuck.utils import replace_argument
from thefuck.specific.git import git_support


@git_support
def match(command):
    return ('error: did you mean `' in command.output
            and '` (with two dashes ?)' in command.output)


@git_support
def get_new_command(command):
    to = command.output.split('`')[1]
    return replace_argument(command.script, to[1:], to)



File: thefuck/rules/npm_missing_script.py

import re
from thefuck.utils import for_app, replace_command
from thefuck.specific.npm import get_scripts, npm_available

enabled_by_default = npm_available


@for_app('npm')
def match(command):
    return (any(part.startswith('ru') for part in command.script_parts)
            and 'npm ERR! missing script: ' in command.output)


def get_new_command(command):
    misspelled_script = re.findall(
        r'.*missing script: (.*)\n', command.output)[0]
    return replace_command(command, misspelled_script, get_scripts())



File: thefuck/rules/terraform_no_command.py

import re
from thefuck.utils import for_app

MISTAKE = r'(?<=Terraform has no command named ")([^"]+)(?="\.)'
FIX = r'(?<=Did you mean ")([^"]+)(?="\?)'


@for_app('terraform')
def match(command):
    return re.search(MISTAKE, command.output) and re.search(FIX, command.output)


def get_new_command(command):
    mistake = re.search(MISTAKE, command.output).group(0)
    fix = re.search(FIX, command.output).group(0)
    return command.script.replace(mistake, fix)



File: thefuck/rules/apt_upgrade.py

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

enabled_by_default = apt_available


@sudo_support
@for_app('apt')
def match(command):
    return command.script == "apt list --upgradable" and len(command.output.strip().split('\n')) > 1


@sudo_support
def get_new_command(command):
    return 'apt upgrade'



File: thefuck/rules/javac.py

"""Appends .java when compiling java files

Example:
 > javac foo
 error: Class names, 'foo', are only accepted if annotation
 processing is explicitly requested

"""
from thefuck.utils import for_app


@for_app('javac')
def match(command):
    return not command.script.endswith('.java')


def get_new_command(command):
    return command.script + '.java'



File: thefuck/rules/path_from_history.py

from collections import Counter
import re
from thefuck.system import Path
from thefuck.utils import (get_valid_history_without_current,
                           memoize, replace_argument)
from thefuck.shells import shell


patterns = [r'no such file or directory: (.*)$',
            r"cannot access '(.*)': No such file or directory",
            r': (.*): No such file or directory',
            r"can't cd to (.*)$"]


@memoize
def _get_destination(command):
    for pattern in patterns:
        found = re.findall(pattern, command.output)
        if found:
            if found[0] in command.script_parts:
                return found[0]


def match(command):
    return bool(_get_destination(command))


def _get_all_absolute_paths_from_history(command):
    counter = Counter()

    for line in get_valid_history_without_current(command):
        splitted = shell.split_command(line)

        for param in splitted[1:]:
            if param.startswith('/') or param.startswith('~'):
                if param.endswith('/'):
                    param = param[:-1]

                counter[param] += 1

    return (path for path, _ in counter.most_common(None))


def get_new_command(command):
    destination = _get_destination(command)
    paths = _get_all_absolute_paths_from_history(command)

    return [replace_argument(command.script, destination, path)
            for path in paths if path.endswith(destination)
            and Path(path).expanduser().exists()]


priority = 800



File: thefuck/rules/sl_ls.py

"""
This happens way too often

When typing really fast cause I'm a 1337 H4X0R,
I often fuck up 'ls' and type 'sl'. No more!
"""


def match(command):
    return command.script == 'sl'


def get_new_command(command):
    return 'ls'



File: thefuck/rules/yarn_command_replaced.py

import re
from thefuck.utils import for_app

regex = re.compile(r'Run "(.*)" instead')


@for_app('yarn', at_least=1)
def match(command):
    return regex.findall(command.output)


def get_new_command(command):
    return regex.findall(command.output)[0]



File: thefuck/rules/man_no_space.py

def match(command):
    return (command.script.startswith(u'man')
            and u'command not found' in command.output.lower())


def get_new_command(command):
    return u'man {}'.format(command.script[3:])


priority = 2000



File: thefuck/rules/git_clone_missing.py

'''
Rule: git_clone_missing

Correct missing `git clone` command when pasting a git URL

```sh
>>> https://github.com/nvbn/thefuck.git
git clone https://github.com/nvbn/thefuck.git
```

Author: Miguel Guthridge
'''
from six.moves.urllib import parse
from thefuck.utils import which


def match(command):
    # We want it to be a URL by itself
    if len(command.script_parts) != 1:
        return False
    # Ensure we got the error we expected
    if which(command.script_parts[0]) or not (
        'No such file or directory' in command.output
        or 'not found' in command.output
        or 'is not recognised as' in command.output
    ):
        return False
    url = parse.urlparse(command.script, scheme='ssh')
    # HTTP URLs need a network address
    if not url.netloc and url.scheme != 'ssh':
        return False
    # SSH needs a username and a splitter between the path
    if url.scheme == 'ssh' and not (
        '@' in command.script
        and ':' in command.script
    ):
        return False
    return url.scheme in ['http', 'https', 'ssh']


def get_new_command(command):
    return 'git clone ' + command.script



File: thefuck/rules/wrong_hyphen_before_subcommand.py

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


@sudo_support
def match(command):
    first_part = command.script_parts[0]
    if "-" not in first_part or first_part in get_all_executables():
        return False
    cmd, _ = first_part.split("-", 1)
    return cmd in get_all_executables()


@sudo_support
def get_new_command(command):
    return command.script.replace("-", " ", 1)


priority = 4500
requires_output = False



File: thefuck/rules/fix_alt_space.py

# -*- encoding: utf-8 -*-

import re
from thefuck.specific.sudo import sudo_support


@sudo_support
def match(command):
    return ('command not found' in command.output.lower()
            and u' ' in command.script)


@sudo_support
def get_new_command(command):
    return re.sub(u' ', ' ', command.script)



File: thefuck/rules/git_tag_force.py

from thefuck.utils import replace_argument
from thefuck.specific.git import git_support


@git_support
def match(command):
    return ('tag' in command.script_parts
            and 'already exists' in command.output)


@git_support
def get_new_command(command):
    return replace_argument(command.script, 'tag', 'tag --force')



File: thefuck/rules/has_exists_script.py

import os
from thefuck.specific.sudo import sudo_support


@sudo_support
def match(command):
    return command.script_parts and os.path.exists(command.script_parts[0]) \
        and 'command not found' in command.output


@sudo_support
def get_new_command(command):
    return u'./{}'.format(command.script)



File: thefuck/rules/brew_reinstall.py

import re
from thefuck.utils import for_app


warning_regex = re.compile(r'Warning: (?:.(?!is ))+ is already installed and '
                           r'up-to-date')
message_regex = re.compile(r'To reinstall (?:(?!, ).)+, run `brew reinstall '
                           r'[^`]+`')


@for_app('brew', at_least=2)
def match(command):
    return ('install' in command.script
            and warning_regex.search(command.output)
            and message_regex.search(command.output))


def get_new_command(command):
    return command.script.replace('install', 'reinstall')



File: thefuck/rules/cd_parent.py

# Adds the missing space between the cd command and the target directory
# when trying to cd to the parent directory.
#
# Does not really save chars, but is fun :D
#
# Example:
# > cd..
# cd..: command not found


def match(command):
    return command.script == 'cd..'


def get_new_command(command):
    return 'cd ..'



File: thefuck/rules/open.py

# Opens URL's in the default web browser
#
# Example:
# > open github.com
# The file ~/github.com does not exist.
# Perhaps you meant 'http://github.com'?
#
from thefuck.shells import shell
from thefuck.utils import eager, for_app


def is_arg_url(command):
    return ('.com' in command.script or
            '.edu' in command.script or
            '.info' in command.script or
            '.io' in command.script or
            '.ly' in command.script or
            '.me' in command.script or
            '.net' in command.script or
            '.org' in command.script or
            '.se' in command.script or
            'www.' in command.script)


@for_app('open', 'xdg-open', 'gnome-open', 'kde-open')
def match(command):
    return (is_arg_url(command) or
            command.output.strip().startswith('The file ') and
            command.output.strip().endswith(' does not exist.'))


@eager
def get_new_command(command):
    output = command.output.strip()
    if is_arg_url(command):
        yield command.script.replace('open ', 'open http://')
    elif output.startswith('The file ') and output.endswith(' does not exist.'):
        arg = command.script.split(' ', 1)[1]
        for option in ['touch', 'mkdir']:
            yield shell.and_(u'{} {}'.format(option, arg), command.script)



File: thefuck/rules/pacman_invalid_option.py

from thefuck.specific.archlinux import archlinux_env
from thefuck.specific.sudo import sudo_support
from thefuck.utils import for_app
import re


@sudo_support
@for_app("pacman")
def match(command):
    return command.output.startswith("error: invalid option '-") and any(
        " -{}".format(option) in command.script for option in "surqfdvt"
    )


def get_new_command(command):
    option = re.findall(r" -[dfqrstuv]", command.script)[0]
    return re.sub(option, option.upper(), command.script)


enabled_by_default = archlinux_env()



File: thefuck/rules/apt_get_search.py

import re
from thefuck.specific.apt import apt_available
from thefuck.utils import for_app

enabled_by_default = apt_available


@for_app('apt-get')
def match(command):
    return command.script.startswith('apt-get search')


def get_new_command(command):
    return re.sub(r'^apt-get', 'apt-cache', command.script)



File: thefuck/rules/dry.py

def match(command):
    split_command = command.script_parts

    return (split_command
            and len(split_command) >= 2
            and split_command[0] == split_command[1])


def get_new_command(command):
    return ' '.join(command.script_parts[1:])


# it should be rare enough to actually have to type twice the same word, so
# this rule can have a higher priority to come before things like "cd cd foo"
priority = 900



File: thefuck/rules/terraform_init.py

from thefuck.shells import shell
from thefuck.utils import for_app


@for_app('terraform')
def match(command):
    return ('this module is not yet installed' in command.output.lower() or
            'initialization required' in command.output.lower()
            )


def get_new_command(command):
    return shell.and_('terraform init', command.script)



File: thefuck/rules/systemctl.py

"""
The confusion in systemctl's param order is massive.
"""
from thefuck.specific.sudo import sudo_support
from thefuck.utils import for_app


@sudo_support
@for_app('systemctl')
def match(command):
    # Catches "Unknown operation 'service'." when executing systemctl with
    # misordered arguments
    cmd = command.script_parts
    return (cmd and 'Unknown operation \'' in command.output and
            len(cmd) - cmd.index('systemctl') == 3)


@sudo_support
def get_new_command(command):
    cmd = command.script_parts[:]
    cmd[-1], cmd[-2] = cmd[-2], cmd[-1]
    return ' '.join(cmd)



File: thefuck/rules/grep_arguments_order.py

import os
from thefuck.utils import for_app


def _get_actual_file(parts):
    for part in parts[1:]:
        if os.path.isfile(part) or os.path.isdir(part):
            return part


@for_app('grep', 'egrep')
def match(command):
    return ': No such file or directory' in command.output \
        and _get_actual_file(command.script_parts)


def get_new_command(command):
    actual_file = _get_actual_file(command.script_parts)
    parts = command.script_parts[::]
    # Moves file to the end of the script:
    parts.remove(actual_file)
    parts.append(actual_file)
    return ' '.join(parts)



File: thefuck/rules/grunt_task_not_found.py

import re
from subprocess import Popen, PIPE
from thefuck.utils import for_app, eager, get_closest, cache

regex = re.compile(r'Warning: Task "(.*)" not found.')


@for_app('grunt')
def match(command):
    return regex.findall(command.output)


@cache('Gruntfile.js')
@eager
def _get_all_tasks():
    proc = Popen(['grunt', '--help'], stdout=PIPE)
    should_yield = False
    for line in proc.stdout.readlines():
        line = line.decode().strip()

        if 'Available tasks' in line:
            should_yield = True
            continue

        if should_yield and not line:
            return

        if '  ' in line:
            yield line.split(' ')[0]


def get_new_command(command):
    misspelled_task = regex.findall(command.output)[0].split(':')[0]
    tasks = _get_all_tasks()
    fixed = get_closest(misspelled_task, tasks)
    return command.script.replace(' {}'.format(misspelled_task),
                                  ' {}'.format(fixed))



File: thefuck/rules/remove_shell_prompt_literal.py

"""Fixes error for commands containing one or more occurrences of the shell
prompt symbol '$'.

This usually happens when commands are copied from documentations
including them in their code blocks.

Example:
> $ git clone https://github.com/nvbn/thefuck.git
bash: $: command not found...
"""

import re


def match(command):
    return (
        "$: command not found" in command.output
        and re.search(r"^[\s]*\$ [\S]+", command.script) is not None
    )


def get_new_command(command):
    return command.script.lstrip("$ ")



File: thefuck/rules/brew_install.py

import re
from thefuck.utils import for_app
from thefuck.specific.brew import brew_available

enabled_by_default = brew_available


def _get_suggestions(str):
    suggestions = str.replace(" or ", ", ").split(", ")
    return suggestions


@for_app('brew', at_least=2)
def match(command):
    is_proper_command = ('install' in command.script and
                         'No available formula' in command.output and
                         'Did you mean' in command.output)
    return is_proper_command


def get_new_command(command):
    matcher = re.search('Warning: No available formula with the name "(?:[^"]+)". Did you mean (.+)\\?', command.output)
    suggestions = _get_suggestions(matcher.group(1))
    return ["brew install " + formula for formula in suggestions]



File: thefuck/rules/choco_install.py

from thefuck.utils import for_app, which


@for_app("choco", "cinst")
def match(command):
    return ((command.script.startswith('choco install') or 'cinst' in command.script_parts)
            and 'Installing the following packages' in command.output)


def get_new_command(command):
    # Find the argument that is the package name
    for script_part in command.script_parts:
        if (
            script_part not in ["choco", "cinst", "install"]
            # Need exact match (bc chocolatey is a package)
            and not script_part.startswith('-')
            # Leading hyphens are parameters; some packages contain them though
            and '=' not in script_part and '/' not in script_part
            # These are certainly parameters
        ):
            return command.script.replace(script_part, script_part + ".install")
    return []


enabled_by_default = bool(which("choco")) or bool(which("cinst"))



File: thefuck/rules/touch.py

import re
from thefuck.shells import shell
from thefuck.utils import for_app


@for_app('touch')
def match(command):
    return 'No such file or directory' in command.output


def get_new_command(command):
    path = re.findall(
        r"touch: (?:cannot touch ')?(.+)/.+'?:", command.output)[0]
    return shell.and_(u'mkdir -p {}'.format(path), command.script)



File: thefuck/rules/dnf_no_such_command.py

import subprocess
import re
from thefuck.specific.sudo import sudo_support
from thefuck.utils import for_app, replace_command
from thefuck.specific.dnf import dnf_available


regex = re.compile(r'No such command: (.*)\.')


@sudo_support
@for_app('dnf')
def match(command):
    return 'no such command' in command.output.lower()


def _parse_operations(help_text_lines):
    operation_regex = re.compile(r'^([a-z-]+) +', re.MULTILINE)
    return operation_regex.findall(help_text_lines)


def _get_operations():
    proc = subprocess.Popen(["dnf", '--help'],
                            stdout=subprocess.PIPE,
                            stderr=subprocess.PIPE)
    lines = proc.stdout.read().decode("utf-8")

    return _parse_operations(lines)


@sudo_support
def get_new_command(command):
    misspelled_command = regex.findall(command.output)[0]
    return replace_command(command, misspelled_command, _get_operations())


enabled_by_default = dnf_available



File: thefuck/rules/whois.py

# -*- encoding: utf-8 -*-
from six.moves.urllib.parse import urlparse
from thefuck.utils import for_app


@for_app('whois', at_least=1)
def match(command):
    """
    What the `whois` command returns depends on the 'Whois server' it contacted
    and is not consistent through different servers. But there can be only two
    types of errors I can think of with `whois`:
        - `whois https://en.wikipedia.org/` → `whois en.wikipedia.org`;
        - `whois en.wikipedia.org` → `whois wikipedia.org`.
    So we match any `whois` command and then:
        - if there is a slash: keep only the FQDN;
        - if there is no slash but there is a point: removes the left-most
          subdomain.

    We cannot either remove all subdomains because we cannot know which part is
    the subdomains and which is the domain, consider:
        - www.google.fr → subdomain: www, domain: 'google.fr';
        - google.co.uk → subdomain: None, domain; 'google.co.uk'.
    """
    return True


def get_new_command(command):
    url = command.script_parts[1]

    if '/' in command.script:
        return 'whois ' + urlparse(url).netloc
    elif '.' in command.script:
        path = urlparse(url).path.split('.')
        return ['whois ' + '.'.join(path[n:]) for n in range(1, len(path))]



File: thefuck/rules/git_rebase_merge_dir.py

from thefuck.utils import get_close_matches
from thefuck.specific.git import git_support


@git_support
def match(command):
    return (' rebase' in command.script and
            'It seems that there is already a rebase-merge directory' in command.output and
            'I wonder if you are in the middle of another rebase' in command.output)


@git_support
def get_new_command(command):
    command_list = ['git rebase --continue', 'git rebase --abort', 'git rebase --skip']
    rm_cmd = command.output.split('\n')[-4]
    command_list.append(rm_cmd.strip())
    return get_close_matches(command.script, command_list, 4, 0)



File: thefuck/rules/git_lfs_mistype.py

import re
from thefuck.utils import get_all_matched_commands, replace_command
from thefuck.specific.git import git_support


@git_support
def match(command):
    '''
    Match a mistyped command
    '''
    return 'lfs' in command.script and 'Did you mean this?' in command.output


@git_support
def get_new_command(command):
    broken_cmd = re.findall(r'Error: unknown command "([^"]*)" for "git-lfs"', command.output)[0]
    matched = get_all_matched_commands(command.output, ['Did you mean', ' for usage.'])
    return replace_command(command, broken_cmd, matched)



File: thefuck/rules/conda_mistype.py

import re
from thefuck.utils import replace_command, for_app


@for_app("conda")
def match(command):
    """
    Match a mistyped command
    """
    return "Did you mean 'conda" in command.output


def get_new_command(command):
    match = re.findall(r"'conda ([^']*)'", command.output)
    broken_cmd = match[0]
    correct_cmd = match[1]
    return replace_command(command, broken_cmd, [correct_cmd])



File: thefuck/rules/heroku_not_command.py

import re
from thefuck.utils import for_app


@for_app('heroku')
def match(command):
    return 'Run heroku _ to run' in command.output


def get_new_command(command):
    return re.findall('Run heroku _ to run ([^.]*)', command.output)[0]



File: thefuck/rules/python_execute.py

# Appends .py when executing python files
#
# Example:
# > python foo
# error: python: can't open file 'foo': [Errno 2] No such file or directory
from thefuck.utils import for_app


@for_app('python')
def match(command):
    return not command.script.endswith('.py')


def get_new_command(command):
    return command.script + '.py'



File: thefuck/rules/no_command.py

from thefuck.utils import get_all_executables, get_close_matches, \
    get_valid_history_without_current, get_closest, which
from thefuck.specific.sudo import sudo_support


@sudo_support
def match(command):
    return (not which(command.script_parts[0])
            and ('not found' in command.output
                 or 'is not recognized as' in command.output)
            and bool(get_close_matches(command.script_parts[0],
                                       get_all_executables())))


def _get_used_executables(command):
    for script in get_valid_history_without_current(command):
        yield script.split(' ')[0]


@sudo_support
def get_new_command(command):
    old_command = command.script_parts[0]

    # One from history:
    already_used = get_closest(
        old_command, _get_used_executables(command),
        fallback_to_first=False)
    if already_used:
        new_cmds = [already_used]
    else:
        new_cmds = []

    # Other from all executables:
    new_cmds += [cmd for cmd in get_close_matches(old_command,
                                                  get_all_executables())
                 if cmd not in new_cmds]

    return [command.script.replace(old_command, cmd, 1) for cmd in new_cmds]


priority = 3000



File: thefuck/rules/fix_file.py

import re
import os
from thefuck.utils import memoize, default_settings
from thefuck.conf import settings
from thefuck.shells import shell


# order is important: only the first match is considered
patterns = (
    # js, node:
    '^    at {file}:{line}:{col}',
    # cargo:
    '^   {file}:{line}:{col}',
    # python, thefuck:
    '^  File "{file}", line {line}',
    # awk:
    '^awk: {file}:{line}:',
    # git
    '^fatal: bad config file line {line} in {file}',
    # llc:
    '^llc: {file}:{line}:{col}:',
    # lua:
    '^lua: {file}:{line}:',
    # fish:
    '^{file} \\(line {line}\\):',
    # bash, sh, ssh:
    '^{file}: line {line}: ',
    # cargo, clang, gcc, go, pep8, rustc:
    '^{file}:{line}:{col}',
    # ghc, make, ruby, zsh:
    '^{file}:{line}:',
    # perl:
    'at {file} line {line}',
)


# for the sake of readability do not use named groups above
def _make_pattern(pattern):
    pattern = pattern.replace('{file}', '(?P<file>[^:\n]+)') \
                     .replace('{line}', '(?P<line>[0-9]+)') \
                     .replace('{col}', '(?P<col>[0-9]+)')
    return re.compile(pattern, re.MULTILINE)


patterns = [_make_pattern(p).search for p in patterns]


@memoize
def _search(output):
    for pattern in patterns:
        m = pattern(output)
        if m and os.path.isfile(m.group('file')):
            return m


def match(command):
    if 'EDITOR' not in os.environ:
        return False

    return _search(command.output)


@default_settings({'fixlinecmd': u'{editor} {file} +{line}',
                   'fixcolcmd': None})
def get_new_command(command):
    m = _search(command.output)

    # Note: there does not seem to be a standard for columns, so they are just
    # ignored by default
    if settings.fixcolcmd and 'col' in m.groupdict():
        editor_call = settings.fixcolcmd.format(editor=os.environ['EDITOR'],
                                                file=m.group('file'),
                                                line=m.group('line'),
                                                col=m.group('col'))
    else:
        editor_call = settings.fixlinecmd.format(editor=os.environ['EDITOR'],
                                                 file=m.group('file'),
                                                 line=m.group('line'))

    return shell.and_(editor_call, command.script)



File: thefuck/rules/brew_cask_dependency.py

from thefuck.utils import for_app, eager
from thefuck.shells import shell
from thefuck.specific.brew import brew_available


@for_app('brew')
def match(command):
    return (u'install' in command.script_parts
            and u'brew cask install' in command.output)


@eager
def _get_cask_install_lines(output):
    for line in output.split('\n'):
        line = line.strip()
        if line.startswith('brew cask install'):
            yield line


def _get_script_for_brew_cask(output):
    cask_install_lines = _get_cask_install_lines(output)
    if len(cask_install_lines) > 1:
        return shell.and_(*cask_install_lines)
    else:
        return cask_install_lines[0]


def get_new_command(command):
    brew_cask_script = _get_script_for_brew_cask(command.output)
    return shell.and_(brew_cask_script, command.script)


enabled_by_default = brew_available



File: thefuck/rules/git_not_command.py

import re
from thefuck.utils import get_all_matched_commands, replace_command
from thefuck.specific.git import git_support


@git_support
def match(command):
    return (" is not a git command. See 'git --help'." in command.output
            and ('The most similar command' in command.output
                 or 'Did you mean' in command.output))


@git_support
def get_new_command(command):
    broken_cmd = re.findall(r"git: '([^']*)' is not a git command",
                            command.output)[0]
    matched = get_all_matched_commands(command.output, ['The most similar command', 'Did you mean'])
    return replace_command(command, broken_cmd, matched)



File: thefuck/rules/cd_mkdir.py

import re
from thefuck.utils import for_app
from thefuck.specific.sudo import sudo_support
from thefuck.shells import shell


@sudo_support
@for_app('cd')
def match(command):
    return (
        command.script.startswith('cd ') and any((
            'no such file or directory' in command.output.lower(),
            'cd: can\'t cd to' in command.output.lower(),
            'does not exist' in command.output.lower()
        )))


@sudo_support
def get_new_command(command):
    repl = shell.and_('mkdir -p \\1', 'cd \\1')
    return re.sub(r'^cd (.*)', repl, command.script)



File: thefuck/rules/python_command.py

from thefuck.specific.sudo import sudo_support
# add 'python' suffix to the command if
#  1) The script does not have execute permission or
#  2) is interpreted as shell script


@sudo_support
def match(command):
    return (command.script_parts
            and command.script_parts[0].endswith('.py')
            and ('Permission denied' in command.output or
                 'command not found' in command.output))


@sudo_support
def get_new_command(command):
    return 'python ' + command.script



File: thefuck/rules/missing_space_before_subcommand.py

from thefuck.utils import get_all_executables, memoize


@memoize
def _get_executable(script_part):
    for executable in get_all_executables():
        if len(executable) > 1 and script_part.startswith(executable):
            return executable


def match(command):
    return (not command.script_parts[0] in get_all_executables()
            and _get_executable(command.script_parts[0]))


def get_new_command(command):
    executable = _get_executable(command.script_parts[0])
    return command.script.replace(executable, u'{} '.format(executable), 1)


priority = 4000



File: thefuck/rules/git_diff_no_index.py

from thefuck.utils import replace_argument
from thefuck.specific.git import git_support


@git_support
def match(command):
    files = [arg for arg in command.script_parts[2:]
             if not arg.startswith('-')]
    return ('diff' in command.script
            and '--no-index' not in command.script
            and len(files) == 2)


@git_support
def get_new_command(command):
    return replace_argument(command.script, 'diff', 'diff --no-index')



File: thefuck/rules/git_branch_list.py

from thefuck.shells import shell
from thefuck.specific.git import git_support


@git_support
def match(command):
    # catches "git branch list" in place of "git branch"
    return (command.script_parts
            and command.script_parts[1:] == 'branch list'.split())


@git_support
def get_new_command(command):
    return shell.and_('git branch --delete list', 'git branch')



File: thefuck/rules/git_hook_bypass.py

from thefuck.utils import replace_argument
from thefuck.specific.git import git_support

hooked_commands = ("am", "commit", "push")


@git_support
def match(command):
    return any(
        hooked_command in command.script_parts for hooked_command in hooked_commands
    )


@git_support
def get_new_command(command):
    hooked_command = next(
        hooked_command
        for hooked_command in hooked_commands
        if hooked_command in command.script_parts
    )
    return replace_argument(
        command.script, hooked_command, hooked_command + " --no-verify"
    )


priority = 1100
requires_output = False



File: thefuck/rules/heroku_multiple_apps.py

import re
from thefuck.utils import for_app


@for_app('heroku')
def match(command):
    return 'https://devcenter.heroku.com/articles/multiple-environments' in command.output


def get_new_command(command):
    apps = re.findall('([^ ]*) \\([^)]*\\)', command.output)
    return [command.script + ' --app ' + app for app in apps]



File: thefuck/rules/quotation_marks.py

# Fixes careless " and ' usage
#
# Example:
# > git commit -m 'My Message"


def match(command):
    return '\'' in command.script and '\"' in command.script


def get_new_command(command):
    return command.script.replace('\'', '\"')



File: thefuck/rules/ag_literal.py

from thefuck.utils import for_app


@for_app('ag')
def match(command):
    return command.output.endswith('run ag with -Q\n')


def get_new_command(command):
    return command.script.replace('ag', 'ag -Q', 1)



File: thefuck/rules/gem_unknown_command.py

import re
import subprocess
from thefuck.utils import for_app, eager, replace_command, cache, which


@for_app('gem')
def match(command):
    return ('ERROR:  While executing gem ... (Gem::CommandLineError)'
            in command.output
            and 'Unknown command' in command.output)


def _get_unknown_command(command):
    return re.findall(r'Unknown command (.*)$', command.output)[0]


@eager
def _get_all_commands():
    proc = subprocess.Popen(['gem', 'help', 'commands'],
                            stdout=subprocess.PIPE)

    for line in proc.stdout.readlines():
        line = line.decode()

        if line.startswith('    '):
            yield line.strip().split(' ')[0]


if which('gem'):
    _get_all_commands = cache(which('gem'))(_get_all_commands)


def get_new_command(command):
    unknown_command = _get_unknown_command(command)
    all_commands = _get_all_commands()
    return replace_command(command, unknown_command, all_commands)



File: thefuck/rules/go_unknown_command.py

from itertools import dropwhile, islice, takewhile
import subprocess

from thefuck.utils import get_closest, replace_argument, for_app, which, cache


def get_golang_commands():
    proc = subprocess.Popen('go', stderr=subprocess.PIPE)
    lines = [line.decode('utf-8').strip() for line in proc.stderr.readlines()]
    lines = dropwhile(lambda line: line != 'The commands are:', lines)
    lines = islice(lines, 2, None)
    lines = takewhile(lambda line: line, lines)
    return [line.split(' ')[0] for line in lines]


if which('go'):
    get_golang_commands = cache(which('go'))(get_golang_commands)


@for_app('go')
def match(command):
    return 'unknown command' in command.output


def get_new_command(command):
    closest_subcommand = get_closest(command.script_parts[1], get_golang_commands())
    return replace_argument(command.script, command.script_parts[1],
                            closest_subcommand)



File: thefuck/rules/apt_get.py

from types import ModuleType
from thefuck.specific.apt import apt_available
from thefuck.utils import memoize, which
from thefuck.shells import shell

try:
    from CommandNotFound import CommandNotFound

    enabled_by_default = apt_available

    if isinstance(CommandNotFound, ModuleType):
        # For ubuntu 18.04+
        _get_packages = CommandNotFound.CommandNotFound().get_packages
    else:
        # For older versions
        _get_packages = CommandNotFound().getPackages
except ImportError:
    enabled_by_default = False


def _get_executable(command):
    if command.script_parts[0] == 'sudo':
        return command.script_parts[1]
    else:
        return command.script_parts[0]


@memoize
def get_package(executable):
    try:
        packages = _get_packages(executable)
        return packages[0][0]
    except IndexError:
        # IndexError is thrown when no matching package is found
        return None


def match(command):
    if 'not found' in command.output or 'not installed' in command.output:
        executable = _get_executable(command)
        return not which(executable) and get_package(executable)
    else:
        return False


def get_new_command(command):
    executable = _get_executable(command)
    name = get_package(executable)
    formatme = shell.and_('sudo apt-get install {}', '{}')
    return formatme.format(name, command.script)



File: thefuck/rules/cargo_no_command.py

import re
from thefuck.utils import replace_argument, for_app


@for_app('cargo', at_least=1)
def match(command):
    return ('no such subcommand' in command.output.lower()
            and 'Did you mean' in command.output)


def get_new_command(command):
    broken = command.script_parts[1]
    fix = re.findall(r'Did you mean `([^`]*)`', command.output)[0]

    return replace_argument(command.script, broken, fix)



File: thefuck/rules/cp_create_destination.py

from thefuck.shells import shell
from thefuck.utils import for_app


@for_app("cp", "mv")
def match(command):
    return (
        "No such file or directory" in command.output
        or command.output.startswith("cp: directory")
        and command.output.rstrip().endswith("does not exist")
    )


def get_new_command(command):
    return shell.and_(u"mkdir -p {}".format(command.script_parts[-1]), command.script)



File: thefuck/rules/git_pull.py

from thefuck.shells import shell
from thefuck.specific.git import git_support


@git_support
def match(command):
    return 'pull' in command.script and 'set-upstream' in command.output


@git_support
def get_new_command(command):
    line = command.output.split('\n')[-3].strip()
    branch = line.split(' ')[-1]
    set_upstream = line.replace('<remote>', 'origin')\
                       .replace('<branch>', branch)
    return shell.and_(set_upstream, command.script)



File: thefuck/rules/brew_unknown_command.py

import os
import re
from thefuck.utils import get_closest, replace_command
from thefuck.specific.brew import get_brew_path_prefix, brew_available

BREW_CMD_PATH = '/Homebrew/Library/Homebrew/cmd'
TAP_PATH = '/Homebrew/Library/Taps'
TAP_CMD_PATH = '/%s/%s/cmd'

enabled_by_default = brew_available


def _get_brew_commands(brew_path_prefix):
    """To get brew default commands on local environment"""
    brew_cmd_path = brew_path_prefix + BREW_CMD_PATH

    return [name[:-3] for name in os.listdir(brew_cmd_path)
            if name.endswith(('.rb', '.sh'))]


def _get_brew_tap_specific_commands(brew_path_prefix):
    """To get tap's specific commands
    https://github.com/Homebrew/homebrew/blob/master/Library/brew.rb#L115"""
    commands = []
    brew_taps_path = brew_path_prefix + TAP_PATH

    for user in _get_directory_names_only(brew_taps_path):
        taps = _get_directory_names_only(brew_taps_path + '/%s' % user)

        # Brew Taps's naming rule
        # https://github.com/Homebrew/homebrew/blob/master/share/doc/homebrew/brew-tap.md#naming-conventions-and-limitations
        taps = (tap for tap in taps if tap.startswith('homebrew-'))
        for tap in taps:
            tap_cmd_path = brew_taps_path + TAP_CMD_PATH % (user, tap)

            if os.path.isdir(tap_cmd_path):
                commands += (name.replace('brew-', '').replace('.rb', '')
                             for name in os.listdir(tap_cmd_path)
                             if _is_brew_tap_cmd_naming(name))

    return commands


def _is_brew_tap_cmd_naming(name):
    return name.startswith('brew-') and name.endswith('.rb')


def _get_directory_names_only(path):
    return [d for d in os.listdir(path)
            if os.path.isdir(os.path.join(path, d))]


def _brew_commands():
    brew_path_prefix = get_brew_path_prefix()
    if brew_path_prefix:
        try:
            return (_get_brew_commands(brew_path_prefix)
                    + _get_brew_tap_specific_commands(brew_path_prefix))
        except OSError:
            pass

    # Failback commands for testing (Based on Homebrew 0.9.5)
    return ['info', 'home', 'options', 'install', 'uninstall',
            'search', 'list', 'update', 'upgrade', 'pin', 'unpin',
            'doctor', 'create', 'edit', 'cask']


def match(command):
    is_proper_command = ('brew' in command.script and
                         'Unknown command' in command.output)

    if is_proper_command:
        broken_cmd = re.findall(r'Error: Unknown command: ([a-z]+)',
                                command.output)[0]
        return bool(get_closest(broken_cmd, _brew_commands()))
    return False


def get_new_command(command):
    broken_cmd = re.findall(r'Error: Unknown command: ([a-z]+)',
                            command.output)[0]
    return replace_command(command, broken_cmd, _brew_commands())



File: thefuck/rules/npm_wrong_command.py

from thefuck.specific.npm import npm_available
from thefuck.utils import replace_argument, for_app, eager, get_closest
from thefuck.specific.sudo import sudo_support

enabled_by_default = npm_available


def _get_wrong_command(script_parts):
    commands = [part for part in script_parts[1:] if not part.startswith('-')]
    if commands:
        return commands[0]


@sudo_support
@for_app('npm')
def match(command):
    return (command.script_parts[0] == 'npm' and
            'where <command> is one of:' in command.output and
            _get_wrong_command(command.script_parts))


@eager
def _get_available_commands(stdout):
    commands_listing = False
    for line in stdout.split('\n'):
        if line.startswith('where <command> is one of:'):
            commands_listing = True
        elif commands_listing:
            if not line:
                break

            for command in line.split(', '):
                stripped = command.strip()
                if stripped:
                    yield stripped


def get_new_command(command):
    npm_commands = _get_available_commands(command.output)
    wrong_command = _get_wrong_command(command.script_parts)
    fixed = get_closest(wrong_command, npm_commands)
    return replace_argument(command.script, wrong_command, fixed)



File: thefuck/rules/cd_cs.py

# -*- encoding: utf-8 -*-

# Redirects cs to cd when there is a typo
# Due to the proximity of the keys - d and s - this seems like a common typo
# ~ > cs /etc/
# cs: command not found
# ~ > fuck
# cd /etc/ [enter/↑/↓/ctrl+c]
# /etc >


def match(command):
    if command.script_parts[0] == 'cs':
        return True


def get_new_command(command):
    return 'cd' + ''.join(command.script[2:])


priority = 900



File: thefuck/rules/az_cli.py

import re

from thefuck.utils import for_app, replace_argument

INVALID_CHOICE = "(?=az)(?:.*): '(.*)' is not in the '.*' command group."
OPTIONS = "^The most similar choice to '.*' is:\n\\s*(.*)$"


@for_app('az')
def match(command):
    return "is not in the" in command.output and "command group" in command.output


def get_new_command(command):
    mistake = re.search(INVALID_CHOICE, command.output).group(1)
    options = re.findall(OPTIONS, command.output, flags=re.MULTILINE)
    return [replace_argument(command.script, mistake, o) for o in options]



File: thefuck/rules/ls_lah.py

from thefuck.utils import for_app


@for_app('ls')
def match(command):
    return command.script_parts and 'ls -' not in command.script


def get_new_command(command):
    command = command.script_parts[:]
    command[0] = 'ls -lah'
    return ' '.join(command)



File: thefuck/rules/brew_uninstall.py

from thefuck.utils import for_app


@for_app('brew', at_least=2)
def match(command):
    return (command.script_parts[1] in ['uninstall', 'rm', 'remove']
            and "brew uninstall --force" in command.output)


def get_new_command(command):
    command_parts = command.script_parts[:]
    command_parts[1] = 'uninstall'
    command_parts.insert(2, '--force')
    return ' '.join(command_parts)



File: thefuck/rules/git_remote_delete.py

import re

from thefuck.specific.git import git_support


@git_support
def match(command):
    return "remote delete" in command.script


@git_support
def get_new_command(command):
    return re.sub(r"delete", "remove", command.script, 1)



File: thefuck/rules/yarn_help.py

import re
from thefuck.utils import for_app
from thefuck.system import open_command


@for_app('yarn', at_least=2)
def match(command):
    return (command.script_parts[1] == 'help'
            and 'for documentation about this command.' in command.output)


def get_new_command(command):
    url = re.findall(
        r'Visit ([^ ]*) for documentation about this command.',
        command.output)[0]

    return open_command(url)



File: thefuck/rules/rm_root.py

from thefuck.specific.sudo import sudo_support

enabled_by_default = False


@sudo_support
def match(command):
    return (command.script_parts
            and {'rm', '/'}.issubset(command.script_parts)
            and '--no-preserve-root' not in command.script
            and '--no-preserve-root' in command.output)


@sudo_support
def get_new_command(command):
    return u'{} --no-preserve-root'.format(command.script)



File: thefuck/rules/cpp11.py

from thefuck.utils import for_app


@for_app('g++', 'clang++')
def match(command):
    return ('This file requires compiler and library support for the '
            'ISO C++ 2011 standard.' in command.output or
            '-Wc++11-extensions' in command.output)


def get_new_command(command):
    return command.script + ' -std=c++11'



File: thefuck/rules/pip_unknown_command.py

import re
from thefuck.utils import replace_argument, for_app
from thefuck.specific.sudo import sudo_support


@sudo_support
@for_app('pip', 'pip2', 'pip3')
def match(command):
    return ('pip' in command.script and
            'unknown command' in command.output and
            'maybe you meant' in command.output)


def get_new_command(command):
    broken_cmd = re.findall(r'ERROR: unknown command "([^"]+)"',
                            command.output)[0]
    new_cmd = re.findall(r'maybe you meant "([^"]+)"', command.output)[0]

    return replace_argument(command.script, broken_cmd, new_cmd)



File: thefuck/rules/brew_update_formula.py

from thefuck.utils import for_app


@for_app('brew', at_least=2)
def match(command):
    return ('update' in command.script
            and "Error: This command updates brew itself" in command.output
            and "Use `brew upgrade" in command.output)


def get_new_command(command):
    return command.script.replace('update', 'upgrade')



File: thefuck/rules/unsudo.py

patterns = ['you cannot perform this operation as root']


def match(command):
    if command.script_parts and command.script_parts[0] != 'sudo':
        return False

    for pattern in patterns:
        if pattern in command.output.lower():
            return True
    return False


def get_new_command(command):
    return ' '.join(command.script_parts[1:])



File: thefuck/rules/grep_recursive.py

from thefuck.utils import for_app


@for_app('grep')
def match(command):
    return 'is a directory' in command.output.lower()


def get_new_command(command):
    return u'grep -r {}'.format(command.script[5:])



File: thefuck/rules/aws_cli.py

import re

from thefuck.utils import for_app, replace_argument

INVALID_CHOICE = "(?<=Invalid choice: ')(.*)(?=', maybe you meant:)"
OPTIONS = "^\\s*\\*\\s(.*)"


@for_app('aws')
def match(command):
    return "usage:" in command.output and "maybe you meant:" in command.output


def get_new_command(command):
    mistake = re.search(INVALID_CHOICE, command.output).group(0)
    options = re.findall(OPTIONS, command.output, flags=re.MULTILINE)
    return [replace_argument(command.script, mistake, o) for o in options]



File: thefuck/rules/gulp_not_task.py

import re
import subprocess
from thefuck.utils import replace_command, for_app, cache


@for_app('gulp')
def match(command):
    return 'is not in your gulpfile' in command.output


@cache('gulpfile.js')
def get_gulp_tasks():
    proc = subprocess.Popen(['gulp', '--tasks-simple'],
                            stdout=subprocess.PIPE)
    return [line.decode('utf-8')[:-1]
            for line in proc.stdout.readlines()]


def get_new_command(command):
    wrong_task = re.findall(r"Task '(\w+)' is not in your gulpfile",
                            command.output)[0]
    return replace_command(command, wrong_task, get_gulp_tasks())



File: thefuck/rules/prove_recursively.py

import os
from thefuck.utils import for_app


def _is_recursive(part):
    if part == '--recurse':
        return True
    elif not part.startswith('--') and part.startswith('-') and 'r' in part:
        return True


def _isdir(part):
    return not part.startswith('-') and os.path.isdir(part)


@for_app('prove')
def match(command):
    return (
        'NOTESTS' in command.output
        and not any(_is_recursive(part) for part in command.script_parts[1:])
        and any(_isdir(part) for part in command.script_parts[1:]))


def get_new_command(command):
    parts = command.script_parts[:]
    parts.insert(1, '-r')
    return u' '.join(parts)



File: thefuck/rules/sudo_command_from_user_path.py

import re
from thefuck.utils import for_app, which, replace_argument


def _get_command_name(command):
    found = re.findall(r'sudo: (.*): command not found', command.output)
    if found:
        return found[0]


@for_app('sudo')
def match(command):
    if 'command not found' in command.output:
        command_name = _get_command_name(command)
        return which(command_name)


def get_new_command(command):
    command_name = _get_command_name(command)
    return replace_argument(command.script, command_name,
                            u'env "PATH=$PATH" {}'.format(command_name))



File: thefuck/rules/git_pull_uncommitted_changes.py

from thefuck.shells import shell
from thefuck.specific.git import git_support


@git_support
def match(command):
    return ('pull' in command.script
            and ('You have unstaged changes' in command.output
                 or 'contains uncommitted changes' in command.output))


@git_support
def get_new_command(command):
    return shell.and_('git stash', 'git pull', 'git stash pop')



File: thefuck/rules/port_already_in_use.py

import re
from subprocess import Popen, PIPE
from thefuck.utils import memoize, which
from thefuck.shells import shell

enabled_by_default = bool(which('lsof'))

patterns = [r"bind on address \('.*', (?P<port>\d+)\)",
            r'Unable to bind [^ ]*:(?P<port>\d+)',
            r"can't listen on port (?P<port>\d+)",
            r'listen EADDRINUSE [^ ]*:(?P<port>\d+)']


@memoize
def _get_pid_by_port(port):
    proc = Popen(['lsof', '-i', ':{}'.format(port)], stdout=PIPE)
    lines = proc.stdout.read().decode().split('\n')
    if len(lines) > 1:
        return lines[1].split()[1]
    else:
        return None


@memoize
def _get_used_port(command):
    for pattern in patterns:
        matched = re.search(pattern, command.output)
        if matched:
            return matched.group('port')


def match(command):
    port = _get_used_port(command)
    return port and _get_pid_by_port(port)


def get_new_command(command):
    port = _get_used_port(command)
    pid = _get_pid_by_port(port)
    return shell.and_(u'kill {}'.format(pid), command.script)



File: thefuck/rules/adb_unknown_command.py

from thefuck.utils import is_app, get_closest, replace_argument


_ADB_COMMANDS = (
    'backup',
    'bugreport',
    'connect',
    'devices',
    'disable-verity',
    'disconnect',
    'enable-verity',
    'emu',
    'forward',
    'get-devpath',
    'get-serialno',
    'get-state',
    'install',
    'install-multiple',
    'jdwp',
    'keygen',
    'kill-server',
    'logcat',
    'pull',
    'push',
    'reboot',
    'reconnect',
    'restore',
    'reverse',
    'root',
    'run-as',
    'shell',
    'sideload',
    'start-server',
    'sync',
    'tcpip',
    'uninstall',
    'unroot',
    'usb',
    'wait-for',
)


def match(command):
    return (is_app(command, 'adb')
            and command.output.startswith('Android Debug Bridge version'))


def get_new_command(command):
    for idx, arg in enumerate(command.script_parts[1:]):
        # allowed params to ADB are a/d/e/s/H/P/L where s, H, P and L take additional args
        # for example 'adb -s 111 logcat' or 'adb -e logcat'
        if not arg[0] == '-' and not command.script_parts[idx] in ('-s', '-H', '-P', '-L'):
            adb_cmd = get_closest(arg, _ADB_COMMANDS)
            return replace_argument(command.script, arg, adb_cmd)



File: thefuck/rules/git_branch_delete.py

from thefuck.utils import replace_argument
from thefuck.specific.git import git_support


@git_support
def match(command):
    return ('branch -d' in command.script
            and 'If you are sure you want to delete it' in command.output)


@git_support
def get_new_command(command):
    return replace_argument(command.script, '-d', '-D')



File: thefuck/rules/history.py

from thefuck.utils import get_close_matches, get_closest, \
    get_valid_history_without_current


def match(command):
    return len(get_close_matches(command.script,
                                 get_valid_history_without_current(command)))


def get_new_command(command):
    return get_closest(command.script,
                       get_valid_history_without_current(command))


priority = 9999



File: thefuck/rules/django_south_ghost.py

def match(command):
    return 'manage.py' in command.script and \
           'migrate' in command.script \
           and 'or pass --delete-ghost-migrations' in command.output


def get_new_command(command):
    return u'{} --delete-ghost-migrations'.format(command.script)



File: thefuck/rules/git_branch_exists.py

import re
from thefuck.shells import shell
from thefuck.specific.git import git_support
from thefuck.utils import eager


@git_support
def match(command):
    return ("fatal: A branch named '" in command.output
            and "' already exists." in command.output)


@git_support
@eager
def get_new_command(command):
    branch_name = re.findall(
        r"fatal: A branch named '(.+)' already exists.", command.output)[0]
    branch_name = branch_name.replace("'", r"\'")
    new_command_templates = [['git branch -d {0}', 'git branch {0}'],
                             ['git branch -d {0}', 'git checkout -b {0}'],
                             ['git branch -D {0}', 'git branch {0}'],
                             ['git branch -D {0}', 'git checkout -b {0}'],
                             ['git checkout {0}']]
    for new_command_template in new_command_templates:
        yield shell.and_(*new_command_template).format(branch_name)



File: thefuck/rules/git_diff_staged.py

from thefuck.utils import replace_argument
from thefuck.specific.git import git_support


@git_support
def match(command):
    return ('diff' in command.script and
            '--staged' not in command.script)


@git_support
def get_new_command(command):
    return replace_argument(command.script, 'diff', 'diff --staged')



File: thefuck/entrypoints/alias.py

import six
from ..conf import settings
from ..logs import warn
from ..shells import shell
from ..utils import which


def _get_alias(known_args):
    if six.PY2:
        warn("The Fuck will drop Python 2 support soon, more details "
             "https://github.com/nvbn/thefuck/issues/685")

    alias = shell.app_alias(known_args.alias)

    if known_args.enable_experimental_instant_mode:
        if six.PY2:
            warn("Instant mode requires Python 3")
        elif not which('script'):
            warn("Instant mode requires `script` app")
        else:
            return shell.instant_mode_alias(known_args.alias)

    return alias


def print_alias(known_args):
    settings.init(known_args)
    print(_get_alias(known_args))



File: thefuck/entrypoints/fix_command.py

from pprint import pformat
import os
import sys
from difflib import SequenceMatcher
from .. import logs, types, const
from ..conf import settings
from ..corrector import get_corrected_commands
from ..exceptions import EmptyCommand
from ..ui import select_command
from ..utils import get_alias, get_all_executables


def _get_raw_command(known_args):
    if known_args.force_command:
        return [known_args.force_command]
    elif not os.environ.get('TF_HISTORY'):
        return known_args.command
    else:
        history = os.environ['TF_HISTORY'].split('\n')[::-1]
        alias = get_alias()
        executables = get_all_executables()
        for command in history:
            diff = SequenceMatcher(a=alias, b=command).ratio()
            if diff < const.DIFF_WITH_ALIAS or command in executables:
                return [command]
    return []


def fix_command(known_args):
    """Fixes previous command. Used when `thefuck` called without arguments."""
    settings.init(known_args)
    with logs.debug_time('Total'):
        logs.debug(u'Run with settings: {}'.format(pformat(settings)))
        raw_command = _get_raw_command(known_args)

        try:
            command = types.Command.from_raw_script(raw_command)
        except EmptyCommand:
            logs.debug('Empty command, nothing to do')
            return

        corrected_commands = get_corrected_commands(command)
        selected_command = select_command(corrected_commands)

        if selected_command:
            selected_command.run(command)
        else:
            sys.exit(1)



File: thefuck/entrypoints/__init__.py




File: thefuck/entrypoints/main.py

# Initialize output before importing any module, that can use colorama.
from ..system import init_output

init_output()

import os  # noqa: E402
import sys  # noqa: E402
from .. import logs  # noqa: E402
from ..argument_parser import Parser  # noqa: E402
from ..utils import get_installation_version  # noqa: E402
from ..shells import shell  # noqa: E402
from .alias import print_alias  # noqa: E402
from .fix_command import fix_command  # noqa: E402


def main():
    parser = Parser()
    known_args = parser.parse(sys.argv)

    if known_args.help:
        parser.print_help()
    elif known_args.version:
        logs.version(get_installation_version(),
                     sys.version.split()[0], shell.info())
    # It's important to check if an alias is being requested before checking if
    # `TF_HISTORY` is in `os.environ`, otherwise it might mess with subshells.
    # Check https://github.com/nvbn/thefuck/issues/921 for reference
    elif known_args.alias:
        print_alias(known_args)
    elif known_args.command or 'TF_HISTORY' in os.environ:
        fix_command(known_args)
    elif known_args.shell_logger:
        try:
            from .shell_logger import shell_logger  # noqa: E402
        except ImportError:
            logs.warn('Shell logger supports only Linux and macOS')
        else:
            shell_logger(known_args.shell_logger)
    else:
        parser.print_usage()



File: thefuck/entrypoints/shell_logger.py

import array
import fcntl
from functools import partial
import mmap
import os
import pty
import signal
import sys
import termios
import tty
from .. import logs, const


def _read(f, fd):
    data = os.read(fd, 1024)
    try:
        f.write(data)
    except ValueError:
        position = const.LOG_SIZE_IN_BYTES - const.LOG_SIZE_TO_CLEAN
        f.move(0, const.LOG_SIZE_TO_CLEAN, position)
        f.seek(position)
        f.write(b'\x00' * const.LOG_SIZE_TO_CLEAN)
        f.seek(position)
    return data


def _set_pty_size(master_fd):
    buf = array.array('h', [0, 0, 0, 0])
    fcntl.ioctl(pty.STDOUT_FILENO, termios.TIOCGWINSZ, buf, True)
    fcntl.ioctl(master_fd, termios.TIOCSWINSZ, buf)


def _spawn(shell, master_read):
    """Create a spawned process.

    Modified version of pty.spawn with terminal size support.

    """
    pid, master_fd = pty.fork()

    if pid == pty.CHILD:
        os.execlp(shell, shell)

    try:
        mode = tty.tcgetattr(pty.STDIN_FILENO)
        tty.setraw(pty.STDIN_FILENO)
        restore = True
    except tty.error:    # This is the same as termios.error
        restore = False

    _set_pty_size(master_fd)
    signal.signal(signal.SIGWINCH, lambda *_: _set_pty_size(master_fd))

    try:
        pty._copy(master_fd, master_read, pty._read)
    except OSError:
        if restore:
            tty.tcsetattr(pty.STDIN_FILENO, tty.TCSAFLUSH, mode)

    os.close(master_fd)
    return os.waitpid(pid, 0)[1]


def shell_logger(output):
    """Logs shell output to the `output`.

    Works like unix script command with `-f` flag.

    """
    if not os.environ.get('SHELL'):
        logs.warn("Shell logger doesn't support your platform.")
        sys.exit(1)

    fd = os.open(output, os.O_CREAT | os.O_TRUNC | os.O_RDWR)
    os.write(fd, b'\x00' * const.LOG_SIZE_IN_BYTES)
    buffer = mmap.mmap(fd, const.LOG_SIZE_IN_BYTES, mmap.MAP_SHARED, mmap.PROT_WRITE)
    return_code = _spawn(os.environ['SHELL'], partial(_read, buffer))

    sys.exit(return_code)



File: thefuck/entrypoints/not_configured.py

# Initialize output before importing any module, that can use colorama.
from ..system import init_output

init_output()

import getpass  # noqa: E402
import os  # noqa: E402
import json  # noqa: E402
from tempfile import gettempdir  # noqa: E402
import time  # noqa: E402
import six  # noqa: E402
from psutil import Process  # noqa: E402
from .. import logs, const  # noqa: E402
from ..shells import shell  # noqa: E402
from ..conf import settings  # noqa: E402
from ..system import Path  # noqa: E402


def _get_shell_pid():
    """Returns parent process pid."""
    proc = Process(os.getpid())

    try:
        return proc.parent().pid
    except TypeError:
        return proc.parent.pid


def _get_not_configured_usage_tracker_path():
    """Returns path of special file where we store latest shell pid."""
    return Path(gettempdir()).joinpath(u'thefuck.last_not_configured_run_{}'.format(
        getpass.getuser(),
    ))


def _record_first_run():
    """Records shell pid to tracker file."""
    info = {'pid': _get_shell_pid(),
            'time': time.time()}

    mode = 'wb' if six.PY2 else 'w'
    with _get_not_configured_usage_tracker_path().open(mode) as tracker:
        json.dump(info, tracker)


def _get_previous_command():
    history = shell.get_history()

    if history:
        return history[-1]
    else:
        return None


def _is_second_run():
    """Returns `True` when we know that `fuck` called second time."""
    tracker_path = _get_not_configured_usage_tracker_path()
    if not tracker_path.exists():
        return False

    current_pid = _get_shell_pid()
    with tracker_path.open('r') as tracker:
        try:
            info = json.load(tracker)
        except ValueError:
            return False

    if not (isinstance(info, dict) and info.get('pid') == current_pid):
        return False

    return (_get_previous_command() == 'fuck' or
            time.time() - info.get('time', 0) < const.CONFIGURATION_TIMEOUT)


def _is_already_configured(configuration_details):
    """Returns `True` when alias already in shell config."""
    path = Path(configuration_details.path).expanduser()
    with path.open('r') as shell_config:
        return configuration_details.content in shell_config.read()


def _configure(configuration_details):
    """Adds alias to shell config."""
    path = Path(configuration_details.path).expanduser()
    with path.open('a') as shell_config:
        shell_config.write(u'\n')
        shell_config.write(configuration_details.content)
        shell_config.write(u'\n')


def main():
    """Shows useful information about how-to configure alias on a first run
    and configure automatically on a second.

    It'll be only visible when user type fuck and when alias isn't configured.

    """
    settings.init()
    configuration_details = shell.how_to_configure()
    if (
        configuration_details and
        configuration_details.can_configure_automatically
    ):
        if _is_already_configured(configuration_details):
            logs.already_configured(configuration_details)
            return
        elif _is_second_run():
            _configure(configuration_details)
            logs.configured_successfully(configuration_details)
            return
        else:
            _record_first_run()

    logs.how_to_configure_alias(configuration_details)



# The Fuck [![Version][version-badge]][version-link] [![Build Status][workflow-badge]][workflow-link] [![Coverage][coverage-badge]][coverage-link] [![MIT License][license-badge]](LICENSE.md)

*The Fuck* is a magnificent app, inspired by a [@liamosaur](https://twitter.com/liamosaur/)
[tweet](https://twitter.com/liamosaur/status/506975850596536320),
that corrects errors in previous console commands.


Is *The Fuck* too slow? [Try the experimental instant mode!](#experimental-instant-mode)

[![gif with examples][examples-link]][examples-link]

More examples:

```bash
➜ apt-get install vim
E: Could not open lock file /var/lib/dpkg/lock - open (13: Permission denied)
E: Unable to lock the administration directory (/var/lib/dpkg/), are you root?

➜ fuck
sudo apt-get install vim [enter/↑/↓/ctrl+c]
[sudo] password for nvbn:
Reading package lists... Done
...
```

```bash
➜ git push
fatal: The current branch master has no upstream branch.
To push the current branch and set the remote as upstream, use

    git push --set-upstream origin master


➜ fuck
git push --set-upstream origin master [enter/↑/↓/ctrl+c]
Counting objects: 9, done.
...
```

```bash
➜ puthon
No command 'puthon' found, did you mean:
 Command 'python' from package 'python-minimal' (main)
 Command 'python' from package 'python3' (main)
zsh: command not found: puthon

➜ fuck
python [enter/↑/↓/ctrl+c]
Python 3.4.2 (default, Oct  8 2014, 13:08:17)
...
```

```bash
➜ git brnch
git: 'brnch' is not a git command. See 'git --help'.

Did you mean this?
    branch

➜ fuck
git branch [enter/↑/↓/ctrl+c]
* master
```

```bash
➜ lein rpl
'rpl' is not a task. See 'lein help'.

Did you mean this?
         repl

➜ fuck
lein repl [enter/↑/↓/ctrl+c]
nREPL server started on port 54848 on host 127.0.0.1 - nrepl://127.0.0.1:54848
REPL-y 0.3.1
...
```

If you're not afraid of blindly running corrected commands, the
`require_confirmation` [settings](#settings) option can be disabled:

```bash
➜ apt-get install vim
E: Could not open lock file /var/lib/dpkg/lock - open (13: Permission denied)
E: Unable to lock the administration directory (/var/lib/dpkg/), are you root?

➜ fuck
sudo apt-get install vim
[sudo] password for nvbn:
Reading package lists... Done
...
```

## Contents

1. [Requirements](#requirements)
2. [Installations](#installation)
3. [Updating](#updating)
4. [How it works](#how-it-works)
5. [Creating your own rules](#creating-your-own-rules)
6. [Settings](#settings)
7. [Third party packages with rules](#third-party-packages-with-rules)
8. [Experimental instant mode](#experimental-instant-mode)
9. [Developing](#developing)
10. [License](#license-mit)

## Requirements

- python (3.5+)
- pip
- python-dev

##### [Back to Contents](#contents)

## Installation

On macOS or Linux, you can install *The Fuck* via [Homebrew][homebrew]:

```bash
brew install thefuck
```

On Ubuntu / Mint, install *The Fuck* with the following commands:
```bash
sudo apt update
sudo apt install python3-dev python3-pip python3-setuptools
pip3 install thefuck --user
```

On FreeBSD, install *The Fuck* with the following commands:
```bash
pkg install thefuck
```

On ChromeOS, install *The Fuck* using [chromebrew](https://github.com/skycocker/chromebrew) with the following command:
```bash
crew install thefuck
```

On Arch based systems, install *The Fuck* with the following command:
```
sudo pacman -S thefuck
```

On other systems, install *The Fuck*  by using `pip`:

```bash
pip install thefuck
```

[Alternatively, you may use an OS package manager (OS X, Ubuntu, Arch).](https://github.com/nvbn/thefuck/wiki/Installation)

<a href='#manual-installation' name='manual-installation'>#</a>
It is recommended that you place this command in your `.bash_profile`,
`.bashrc`, `.zshrc` or other startup script:

```bash
eval $(thefuck --alias)
# You can use whatever you want as an alias, like for Mondays:
eval $(thefuck --alias FUCK)
```

[Or in your shell config (Bash, Zsh, Fish, Powershell, tcsh).](https://github.com/nvbn/thefuck/wiki/Shell-aliases)

Changes are only available in a new shell session. To make changes immediately
available, run `source ~/.bashrc` (or your shell config file like `.zshrc`).

To run fixed commands without confirmation, use the `--yeah` option (or just `-y` for short, or `--hard` if you're especially frustrated):

```bash
fuck --yeah
```

To fix commands recursively until succeeding, use the `-r` option:

```bash
fuck -r
```

##### [Back to Contents](#contents)

## Updating

```bash
pip3 install thefuck --upgrade
```

**Note: Alias functionality was changed in v1.34 of *The Fuck***

## Uninstall

To remove *The Fuck*, reverse the installation process:
- erase or comment *thefuck* alias line from your Bash, Zsh, Fish, Powershell, tcsh, ... shell config
- use your package manager (brew, pip3, pkg, crew, pip) to uninstall the binaries

## How it works

*The Fuck* attempts to match the previous command with a rule. If a match is
found, a new command is created using the matched rule and executed. The
following rules are enabled by default:

* `adb_unknown_command` &ndash; fixes misspelled commands like `adb logcta`;
* `ag_literal` &ndash; adds `-Q` to `ag` when suggested;
* `aws_cli` &ndash; fixes misspelled commands like `aws dynamdb scan`;
* `az_cli` &ndash; fixes misspelled commands like `az providers`;
* `cargo` &ndash; runs `cargo build` instead of `cargo`;
* `cargo_no_command` &ndash; fixes wrong commands like `cargo buid`;
* `cat_dir` &ndash; replaces `cat` with `ls` when you try to `cat` a directory;
* `cd_correction` &ndash; spellchecks and corrects failed cd commands;
* `cd_cs` &ndash; changes `cs` to `cd`;
* `cd_mkdir` &ndash; creates directories before cd'ing into them;
* `cd_parent` &ndash; changes `cd..` to `cd ..`;
* `chmod_x` &ndash; adds execution bit;
* `choco_install` &ndash; appends common suffixes for chocolatey packages;
* `composer_not_command` &ndash; fixes composer command name;
* `conda_mistype` &ndash; fixes conda commands;
* `cp_create_destination` &ndash; creates a new directory when you attempt to `cp` or `mv` to a non-existent one
* `cp_omitting_directory` &ndash; adds `-a` when you `cp` directory;
* `cpp11` &ndash; adds missing `-std=c++11` to `g++` or `clang++`;
* `dirty_untar` &ndash; fixes `tar x` command that untarred in the current directory;
* `dirty_unzip` &ndash; fixes `unzip` command that unzipped in the current directory;
* `django_south_ghost` &ndash; adds `--delete-ghost-migrations` to failed because ghosts django south migration;
* `django_south_merge` &ndash; adds `--merge` to inconsistent django south migration;
* `docker_login` &ndash; executes a `docker login` and repeats the previous command;
* `docker_not_command` &ndash; fixes wrong docker commands like `docker tags`;
* `docker_image_being_used_by_container` &dash; removes the container that is using the image before removing the image;
* `dry` &ndash; fixes repetitions like `git git push`;
* `fab_command_not_found` &ndash; fixes misspelled fabric commands;
* `fix_alt_space` &ndash; replaces Alt+Space with Space character;
* `fix_file` &ndash; opens a file with an error in your `$EDITOR`;
* `gem_unknown_command` &ndash; fixes wrong `gem` commands;
* `git_add` &ndash; fixes *"pathspec 'foo' did not match any file(s) known to git."*;
* `git_add_force` &ndash; adds `--force` to `git add <pathspec>...` when paths are .gitignore'd;
* `git_bisect_usage` &ndash; fixes `git bisect strt`, `git bisect goood`, `git bisect rset`, etc. when bisecting;
* `git_branch_delete` &ndash; changes `git branch -d` to `git branch -D`;
* `git_branch_delete_checked_out` &ndash; changes `git branch -d` to `git checkout master && git branch -D` when trying to delete a checked out branch;
* `git_branch_exists` &ndash; offers `git branch -d foo`, `git branch -D foo` or `git checkout foo` when creating a branch that already exists;
* `git_branch_list` &ndash; catches `git branch list` in place of `git branch` and removes created branch;
* `git_branch_0flag` &ndash; fixes commands such as `git branch 0v` and `git branch 0r` removing the created branch;
* `git_checkout` &ndash; fixes branch name or creates new branch;
* `git_clone_git_clone` &ndash; replaces `git clone git clone ...` with `git clone ...`
* `git_clone_missing` &ndash; adds `git clone` to URLs that appear to link to a git repository.
* `git_commit_add` &ndash; offers `git commit -a ...` or `git commit -p ...` after previous commit if it failed because nothing was staged;
* `git_commit_amend` &ndash; offers `git commit --amend` after previous commit;
* `git_commit_reset` &ndash; offers `git reset HEAD~` after previous commit;
* `git_diff_no_index` &ndash; adds `--no-index` to previous `git diff` on untracked files;
* `git_diff_staged` &ndash; adds `--staged` to previous `git diff` with unexpected output;
* `git_fix_stash` &ndash; fixes `git stash` commands (misspelled subcommand and missing `save`);
* `git_flag_after_filename` &ndash; fixes `fatal: bad flag '...' after filename`
* `git_help_aliased` &ndash; fixes `git help <alias>` commands replacing <alias> with the aliased command;
* `git_hook_bypass` &ndash; adds `--no-verify` flag previous to `git am`, `git commit`, or `git push` command;
* `git_lfs_mistype` &ndash; fixes mistyped `git lfs <command>` commands;
* `git_main_master` &ndash; fixes incorrect branch name between `main` and `master`
* `git_merge` &ndash; adds remote to branch names;
* `git_merge_unrelated` &ndash; adds `--allow-unrelated-histories` when required
* `git_not_command` &ndash; fixes wrong git commands like `git brnch`;
* `git_pull` &ndash; sets upstream before executing previous `git pull`;
* `git_pull_clone` &ndash; clones instead of pulling when the repo does not exist;
* `git_pull_uncommitted_changes` &ndash; stashes changes before pulling and pops them afterwards;
* `git_push` &ndash; adds `--set-upstream origin $branch` to previous failed `git push`;
* `git_push_different_branch_names` &ndash; fixes pushes when local branch name does not match remote branch name;
* `git_push_pull` &ndash; runs `git pull` when `push` was rejected;
* `git_push_without_commits` &ndash; creates an initial commit if you forget and only `git add .`, when setting up a new project;
* `git_rebase_no_changes` &ndash; runs `git rebase --skip` instead of `git rebase --continue` when there are no changes;
* `git_remote_delete` &ndash; replaces `git remote delete remote_name` with `git remote remove remote_name`;
* `git_rm_local_modifications` &ndash; adds `-f` or `--cached` when you try to `rm` a locally modified file;
* `git_rm_recursive` &ndash; adds `-r` when you try to `rm` a directory;
* `git_rm_staged` &ndash;  adds `-f` or `--cached` when you try to `rm` a file with staged changes
* `git_rebase_merge_dir` &ndash; offers `git rebase (--continue | --abort | --skip)` or removing the `.git/rebase-merge` dir when a rebase is in progress;
* `git_remote_seturl_add` &ndash; runs `git remote add` when `git remote set_url` on nonexistent remote;
* `git_stash` &ndash; stashes your local modifications before rebasing or switching branch;
* `git_stash_pop` &ndash; adds your local modifications before popping stash, then resets;
* `git_tag_force` &ndash; adds `--force` to `git tag <tagname>` when the tag already exists;
* `git_two_dashes` &ndash; adds a missing dash to commands like `git commit -amend` or `git rebase -continue`;
* `go_run` &ndash; appends `.go` extension when compiling/running Go programs;
* `go_unknown_command` &ndash; fixes wrong `go` commands, for example `go bulid`;
* `gradle_no_task` &ndash; fixes not found or ambiguous `gradle` task;
* `gradle_wrapper` &ndash; replaces `gradle` with `./gradlew`;
* `grep_arguments_order` &ndash; fixes `grep` arguments order for situations like `grep -lir . test`;
* `grep_recursive` &ndash; adds `-r` when you try to `grep` directory;
* `grunt_task_not_found` &ndash; fixes misspelled `grunt` commands;
* `gulp_not_task` &ndash; fixes misspelled `gulp` tasks;
* `has_exists_script` &ndash; prepends `./` when script/binary exists;
* `heroku_multiple_apps` &ndash; adds `--app <app>` to `heroku` commands like `heroku pg`;
* `heroku_not_command` &ndash; fixes wrong `heroku` commands like `heroku log`;
* `history` &ndash; tries to replace command with the most similar command from history;
* `hostscli` &ndash; tries to fix `hostscli` usage;
* `ifconfig_device_not_found` &ndash; fixes wrong device names like `wlan0` to `wlp2s0`;
* `java` &ndash; removes `.java` extension when running Java programs;
* `javac` &ndash; appends missing `.java` when compiling Java files;
* `lein_not_task` &ndash; fixes wrong `lein` tasks like `lein rpl`;
* `long_form_help` &ndash; changes `-h` to `--help` when the short form version is not supported
* `ln_no_hard_link` &ndash; catches hard link creation on directories, suggest symbolic link;
* `ln_s_order` &ndash; fixes `ln -s` arguments order;
* `ls_all` &ndash; adds `-A` to `ls` when output is empty;
* `ls_lah` &ndash; adds `-lah` to `ls`;
* `man` &ndash; changes manual section;
* `man_no_space` &ndash; fixes man commands without spaces, for example `mandiff`;
* `mercurial` &ndash; fixes wrong `hg` commands;
* `missing_space_before_subcommand` &ndash; fixes command with missing space like `npminstall`;
* `mkdir_p` &ndash; adds `-p` when you try to create a directory without a parent;
* `mvn_no_command` &ndash; adds `clean package` to `mvn`;
* `mvn_unknown_lifecycle_phase` &ndash; fixes misspelled life cycle phases with `mvn`;
* `npm_missing_script` &ndash; fixes `npm` custom script name in `npm run-script <script>`;
* `npm_run_script` &ndash; adds missing `run-script` for custom `npm` scripts;
* `npm_wrong_command` &ndash; fixes wrong npm commands like `npm urgrade`;
* `no_command` &ndash; fixes wrong console commands, for example `vom/vim`;
* `no_such_file` &ndash; creates missing directories with `mv` and `cp` commands;
* `omnienv_no_such_command` &ndash; fixes wrong commands for `goenv`, `nodenv`, `pyenv` and `rbenv` (eg.: `pyenv isntall` or `goenv list`);
* `open` &ndash; either prepends `http://` to address passed to `open` or creates a new file or directory and passes it to `open`;
* `pip_install` &ndash; fixes permission issues with `pip install` commands by adding `--user` or prepending `sudo` if necessary;
* `pip_unknown_command` &ndash; fixes wrong `pip` commands, for example `pip instatl/pip install`;
* `php_s` &ndash; replaces `-s` by `-S` when trying to run a local php server;
* `port_already_in_use` &ndash; kills process that bound port;
* `prove_recursively` &ndash; adds `-r` when called with directory;
* `python_command` &ndash; prepends `python` when you try to run non-executable/without `./` python script;
* `python_execute` &ndash; appends missing `.py` when executing Python files;
* `python_module_error` &ndash; fixes ModuleNotFoundError by trying to `pip install` that module;
* `quotation_marks` &ndash; fixes uneven usage of `'` and `"` when containing args';
* `path_from_history` &ndash; replaces not found path with a similar absolute path from history;
* `rails_migrations_pending` &ndash; runs pending migrations;
* `react_native_command_unrecognized` &ndash; fixes unrecognized `react-native` commands;
* `remove_shell_prompt_literal` &ndash; removes leading shell prompt symbol `$`, common when copying commands from documentations;
* `remove_trailing_cedilla` &ndash; removes trailing cedillas `ç`, a common typo for European keyboard layouts;
* `rm_dir` &ndash; adds `-rf` when you try to remove a directory;
* `scm_correction` &ndash; corrects wrong scm like `hg log` to `git log`;
* `sed_unterminated_s` &ndash; adds missing '/' to `sed`'s `s` commands;
* `sl_ls` &ndash; changes `sl` to `ls`;
* `ssh_known_hosts` &ndash; removes host from `known_hosts` on warning;
* `sudo` &ndash; prepends `sudo` to the previous command if it failed because of permissions;
* `sudo_command_from_user_path` &ndash; runs commands from users `$PATH` with `sudo`;
* `switch_lang` &ndash; switches command from your local layout to en;
* `systemctl` &ndash; correctly orders parameters of confusing `systemctl`;
* `terraform_init.py` &ndash; runs `terraform init` before plan or apply;
* `terraform_no_command.py` &ndash; fixes unrecognized `terraform` commands;
* `test.py` &ndash; runs `pytest` instead of `test.py`;
* `touch` &ndash; creates missing directories before "touching";
* `tsuru_login` &ndash; runs `tsuru login` if not authenticated or session expired;
* `tsuru_not_command` &ndash; fixes wrong `tsuru` commands like `tsuru shell`;
* `tmux` &ndash; fixes `tmux` commands;
* `unknown_command` &ndash; fixes hadoop hdfs-style "unknown command", for example adds missing '-' to the command on `hdfs dfs ls`;
* `unsudo` &ndash; removes `sudo` from previous command if a process refuses to run on superuser privilege.
* `vagrant_up` &ndash; starts up the vagrant instance;
* `whois` &ndash; fixes `whois` command;
* `workon_doesnt_exists` &ndash; fixes `virtualenvwrapper` env name os suggests to create new.
* `wrong_hyphen_before_subcommand` &ndash; removes an improperly placed hyphen (`apt-install` -> `apt install`, `git-log` -> `git log`, etc.)
* `yarn_alias` &ndash; fixes aliased `yarn` commands like `yarn ls`;
* `yarn_command_not_found` &ndash; fixes misspelled `yarn` commands;
* `yarn_command_replaced` &ndash; fixes replaced `yarn` commands;
* `yarn_help` &ndash; makes it easier to open `yarn` documentation;

##### [Back to Contents](#contents)

The following rules are enabled by default on specific platforms only:

* `apt_get` &ndash; installs app from apt if it not installed (requires `python-commandnotfound` / `python3-commandnotfound`);
* `apt_get_search` &ndash; changes trying to search using `apt-get` with searching using `apt-cache`;
* `apt_invalid_operation` &ndash; fixes invalid `apt` and `apt-get` calls, like `apt-get isntall vim`;
* `apt_list_upgradable` &ndash; helps you run `apt list --upgradable` after `apt update`;
* `apt_upgrade` &ndash; helps you run `apt upgrade` after `apt list --upgradable`;
* `brew_cask_dependency` &ndash; installs cask dependencies;
* `brew_install` &ndash; fixes formula name for `brew install`;
* `brew_reinstall` &ndash; turns `brew install <formula>` into `brew reinstall <formula>`;
* `brew_link` &ndash; adds `--overwrite --dry-run` if linking fails;
* `brew_uninstall` &ndash; adds `--force` to `brew uninstall` if multiple versions were installed;
* `brew_unknown_command` &ndash; fixes wrong brew commands, for example `brew docto/brew doctor`;
* `brew_update_formula` &ndash; turns `brew update <formula>` into `brew upgrade <formula>`;
* `dnf_no_such_command` &ndash; fixes mistyped DNF commands;
* `nixos_cmd_not_found` &ndash; installs apps on NixOS;
* `pacman` &ndash; installs app with `pacman` if it is not installed (uses `yay`, `pikaur` or `yaourt` if available);
* `pacman_invalid_option` &ndash; replaces lowercase `pacman` options with uppercase.
* `pacman_not_found` &ndash; fixes package name with `pacman`, `yay`, `pikaur` or `yaourt`.
* `yum_invalid_operation` &ndash; fixes invalid `yum` calls, like `yum isntall vim`;

The following commands are bundled with *The Fuck*, but are not enabled by
default:

* `git_push_force` &ndash; adds `--force-with-lease` to a `git push` (may conflict with `git_push_pull`);
* `rm_root` &ndash; adds `--no-preserve-root` to `rm -rf /` command.

##### [Back to Contents](#contents)

## Creating your own rules

To add your own rule, create a file named `your-rule-name.py`
in `~/.config/thefuck/rules`. The rule file must contain two functions:

```python
match(command: Command) -> bool
get_new_command(command: Command) -> str | list[str]
```

Additionally, rules can contain optional functions:

```python
side_effect(old_command: Command, fixed_command: str) -> None
```
Rules can also contain the optional variables `enabled_by_default`, `requires_output` and `priority`.

`Command` has three attributes: `script`, `output` and `script_parts`.
Your rule should not change `Command`.


**Rules api changed in 3.0:** To access a rule's settings, import it with
 `from thefuck.conf import settings`

`settings` is a special object assembled from `~/.config/thefuck/settings.py`,
and values from env ([see more below](#settings)).

A simple example rule for running a script with `sudo`:

```python
def match(command):
    return ('permission denied' in command.output.lower()
            or 'EACCES' in command.output)


def get_new_command(command):
    return 'sudo {}'.format(command.script)

# Optional:
enabled_by_default = True

def side_effect(command, fixed_command):
    subprocess.call('chmod 777 .', shell=True)

priority = 1000  # Lower first, default is 1000

requires_output = True
```

[More examples of rules](https://github.com/nvbn/thefuck/tree/master/thefuck/rules),
[utility functions for rules](https://github.com/nvbn/thefuck/tree/master/thefuck/utils.py),
[app/os-specific helpers](https://github.com/nvbn/thefuck/tree/master/thefuck/specific/).

##### [Back to Contents](#contents)

## Settings

Several *The Fuck* parameters can be changed in the file `$XDG_CONFIG_HOME/thefuck/settings.py`
(`$XDG_CONFIG_HOME` defaults to `~/.config`):

* `rules` &ndash; list of enabled rules, by default `thefuck.const.DEFAULT_RULES`;
* `exclude_rules` &ndash; list of disabled rules, by default `[]`;
* `require_confirmation` &ndash; requires confirmation before running new command, by default `True`;
* `wait_command` &ndash; the max amount of time in seconds for getting previous command output;
* `no_colors` &ndash; disable colored output;
* `priority` &ndash; dict with rules priorities, rule with lower `priority` will be matched first;
* `debug` &ndash; enables debug output, by default `False`;
* `history_limit` &ndash; the numeric value of how many history commands will be scanned, like `2000`;
* `alter_history` &ndash; push fixed command to history, by default `True`;
* `wait_slow_command` &ndash; max amount of time in seconds for getting previous command output if it in `slow_commands` list;
* `slow_commands` &ndash; list of slow commands;
* `num_close_matches` &ndash; the maximum number of close matches to suggest, by default `3`.
* `excluded_search_path_prefixes` &ndash; path prefixes to ignore when searching for commands, by default `[]`.

An example of `settings.py`:

```python
rules = ['sudo', 'no_command']
exclude_rules = ['git_push']
require_confirmation = True
wait_command = 10
no_colors = False
priority = {'sudo': 100, 'no_command': 9999}
debug = False
history_limit = 9999
wait_slow_command = 20
slow_commands = ['react-native', 'gradle']
num_close_matches = 5
```

Or via environment variables:

* `THEFUCK_RULES` &ndash; list of enabled rules, like `DEFAULT_RULES:rm_root` or `sudo:no_command`;
* `THEFUCK_EXCLUDE_RULES` &ndash; list of disabled rules, like `git_pull:git_push`;
* `THEFUCK_REQUIRE_CONFIRMATION` &ndash; require confirmation before running new command, `true/false`;
* `THEFUCK_WAIT_COMMAND` &ndash; the max amount of time in seconds for getting previous command output;
* `THEFUCK_NO_COLORS` &ndash; disable colored output, `true/false`;
* `THEFUCK_PRIORITY` &ndash; priority of the rules, like `no_command=9999:apt_get=100`,
rule with lower `priority` will be matched first;
* `THEFUCK_DEBUG` &ndash; enables debug output, `true/false`;
* `THEFUCK_HISTORY_LIMIT` &ndash; how many history commands will be scanned, like `2000`;
* `THEFUCK_ALTER_HISTORY` &ndash; push fixed command to history `true/false`;
* `THEFUCK_WAIT_SLOW_COMMAND` &ndash; the max amount of time in seconds for getting previous command output if it in `slow_commands` list;
* `THEFUCK_SLOW_COMMANDS` &ndash; list of slow commands, like `lein:gradle`;
* `THEFUCK_NUM_CLOSE_MATCHES` &ndash; the maximum number of close matches to suggest, like `5`.
* `THEFUCK_EXCLUDED_SEARCH_PATH_PREFIXES` &ndash; path prefixes to ignore when searching for commands, by default `[]`.

For example:

```bash
export THEFUCK_RULES='sudo:no_command'
export THEFUCK_EXCLUDE_RULES='git_pull:git_push'
export THEFUCK_REQUIRE_CONFIRMATION='true'
export THEFUCK_WAIT_COMMAND=10
export THEFUCK_NO_COLORS='false'
export THEFUCK_PRIORITY='no_command=9999:apt_get=100'
export THEFUCK_HISTORY_LIMIT='2000'
export THEFUCK_NUM_CLOSE_MATCHES='5'
```

##### [Back to Contents](#contents)

## Third-party packages with rules

If you'd like to make a specific set of non-public rules, but would still like
to share them with others, create a package named `thefuck_contrib_*` with
the following structure:

```
thefuck_contrib_foo
  thefuck_contrib_foo
    rules
      __init__.py
      *third-party rules*
    __init__.py
    *third-party-utils*
  setup.py
```

*The Fuck* will find rules located in the `rules` module.

##### [Back to Contents](#contents)

## Experimental instant mode

The default behavior of *The Fuck* requires time to re-run previous commands.
When in instant mode, *The Fuck* saves time by logging output with [script](https://en.wikipedia.org/wiki/Script_(Unix)),
then reading the log.

[![gif with instant mode][instant-mode-gif-link]][instant-mode-gif-link]

Currently, instant mode only supports Python 3 with bash or zsh. zsh's autocorrect function also needs to be disabled in order for thefuck to work properly.

To enable instant mode, add `--enable-experimental-instant-mode`
to the alias initialization in `.bashrc`, `.bash_profile` or `.zshrc`.

For example:

```bash
eval $(thefuck --alias --enable-experimental-instant-mode)
```

##### [Back to Contents](#contents)

## Developing

See [CONTRIBUTING.md](CONTRIBUTING.md)

## License MIT
Project License can be found [here](LICENSE.md).


[version-badge]:   https://img.shields.io/pypi/v/thefuck.svg?label=version
[version-link]:    https://pypi.python.org/pypi/thefuck/
[workflow-badge]:  https://github.com/nvbn/thefuck/workflows/Tests/badge.svg
[workflow-link]:   https://github.com/nvbn/thefuck/actions?query=workflow%3ATests
[coverage-badge]:  https://img.shields.io/coveralls/nvbn/thefuck.svg
[coverage-link]:   https://coveralls.io/github/nvbn/thefuck
[license-badge]:   https://img.shields.io/badge/license-MIT-007EC7.svg
[examples-link]:   https://raw.githubusercontent.com/nvbn/thefuck/master/example.gif
[instant-mode-gif-link]:   https://raw.githubusercontent.com/nvbn/thefuck/master/example_instant_mode.gif
[homebrew]:        https://brew.sh/

##### [Back to Contents](#contents)
