mirror of
				https://github.com/nvbn/thefuck.git
				synced 2025-11-04 09:02:08 +00:00 
			
		
		
		
	Compare commits
	
		
			83 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					e5255c3278 | ||
| 
						 | 
					d44b11fbd8 | ||
| 
						 | 
					3472026d5e | ||
| 
						 | 
					bf3c16816d | ||
| 
						 | 
					6fac0622e5 | ||
| 
						 | 
					1b694fae7b | ||
| 
						 | 
					2ebfb92760 | ||
| 
						 | 
					9cb04ac631 | ||
| 
						 | 
					5504b905f3 | ||
| 
						 | 
					e707728fd5 | ||
| 
						 | 
					3d98aad5df | ||
| 
						 | 
					b72ad2907f | ||
| 
						 | 
					7a57355e7e | ||
| 
						 | 
					1132015e60 | ||
| 
						 | 
					0ecc86eda6 | ||
| 
						 | 
					c4848d1816 | ||
| 
						 | 
					31becc9456 | ||
| 
						 | 
					cd3a3cd823 | ||
| 
						 | 
					f9b30ae2d3 | ||
| 
						 | 
					832ef96188 | ||
| 
						 | 
					20e678a38a | ||
| 
						 | 
					f76d2061d1 | ||
| 
						 | 
					16ec6a7d2a | ||
| 
						 | 
					6c4333944f | ||
| 
						 | 
					31f5185642 | ||
| 
						 | 
					d71dbc5de4 | ||
| 
						 | 
					fabef80056 | ||
| 
						 | 
					b4c4fdf706 | ||
| 
						 | 
					d267488520 | ||
| 
						 | 
					e31124335f | ||
| 
						 | 
					71a5182b9a | ||
| 
						 | 
					6a096155dc | ||
| 
						 | 
					5742d2d910 | ||
| 
						 | 
					754bb3e21f | ||
| 
						 | 
					2bbba9a0c8 | ||
| 
						 | 
					b978c3793e | ||
| 
						 | 
					8a83b30e73 | ||
| 
						 | 
					fd20a3f832 | ||
| 
						 | 
					b6ed499103 | ||
| 
						 | 
					76600cf40a | ||
| 
						 | 
					e62666181a | ||
| 
						 | 
					c88b0792b8 | ||
| 
						 | 
					06a89427e2 | ||
| 
						 | 
					3a134f250d | ||
| 
						 | 
					b54cdf7c49 | ||
| 
						 | 
					1b05a497e8 | ||
| 
						 | 
					79602383ec | ||
| 
						 | 
					84c42168df | ||
| 
						 | 
					f53d772ac3 | ||
| 
						 | 
					93d4a4fc3a | ||
| 
						 | 
					2cb23b1805 | ||
| 
						 | 
					33f28cf76d | ||
| 
						 | 
					6322dbd9ed | ||
| 
						 | 
					fc09818351 | ||
| 
						 | 
					2788ef1471 | ||
| 
						 | 
					ef3aabe7c5 | ||
| 
						 | 
					2af54d036d | ||
| 
						 | 
					99c10b50ff | ||
| 
						 | 
					802fcd96fd | ||
| 
						 | 
					900e83e028 | ||
| 
						 | 
					d41cbb6810 | ||
| 
						 | 
					b36cf59b46 | ||
| 
						 | 
					cfa831c88d | ||
| 
						 | 
					818d06fb95 | ||
| 
						 | 
					c3eca8234a | ||
| 
						 | 
					d47ff8cbf2 | ||
| 
						 | 
					1a52e98fbd | ||
| 
						 | 
					53c11d2ef4 | ||
| 
						 | 
					beda1854cf | ||
| 
						 | 
					7532c65c62 | ||
| 
						 | 
					ec37998a10 | ||
| 
						 | 
					58d5eff6d0 | ||
| 
						 | 
					d28567bb31 | ||
| 
						 | 
					b016bb2255 | ||
| 
						 | 
					bf109ee548 | ||
| 
						 | 
					1aaaca1220 | ||
| 
						 | 
					b096560469 | ||
| 
						 | 
					5b1f3ff816 | ||
| 
						 | 
					c5f7c89222 | ||
| 
						 | 
					e61271dae3 | ||
| 
						 | 
					bddb43b987 | ||
| 
						 | 
					b22a3ac891 | ||
| 
						 | 
					f4cc88f6c7 | 
@@ -1 +1,2 @@
 | 
			
		||||
include LICENSE.md
 | 
			
		||||
include fastentrypoints.py
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										44
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										44
									
								
								README.md
									
									
									
									
									
								
							@@ -4,6 +4,8 @@ Magnificent app which corrects your previous console command,
 | 
			
		||||
inspired by a [@liamosaur](https://twitter.com/liamosaur/)
 | 
			
		||||
[tweet](https://twitter.com/liamosaur/status/506975850596536320).
 | 
			
		||||
 | 
			
		||||
The Fuck is too slow? [Try experimental instant mode!](#experimental-instant-mode) 
 | 
			
		||||
 | 
			
		||||
[![gif with examples][examples-link]][examples-link]
 | 
			
		||||
 | 
			
		||||
Few more examples:
 | 
			
		||||
@@ -106,13 +108,13 @@ On Ubuntu you can install `The Fuck` with:
 | 
			
		||||
```bash
 | 
			
		||||
sudo apt update
 | 
			
		||||
sudo apt install python3-dev python3-pip
 | 
			
		||||
pip3 install --user thefuck
 | 
			
		||||
sudo pip3 install thefuck
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
On other systems you can install `The Fuck` with `pip`:
 | 
			
		||||
 | 
			
		||||
```bash
 | 
			
		||||
pip install --user thefuck
 | 
			
		||||
pip install thefuck
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
[Or using an OS package manager (OS X, Ubuntu, Arch).](https://github.com/nvbn/thefuck/wiki/Installation)
 | 
			
		||||
@@ -130,16 +132,22 @@ eval $(thefuck --alias FUCK)
 | 
			
		||||
Changes will be available only in a new shell session.
 | 
			
		||||
To make them available immediately, run `source ~/.bashrc` (or your shell config file like `.zshrc`).
 | 
			
		||||
 | 
			
		||||
If you want separate alias for running fixed command without confirmation you can use alias like:
 | 
			
		||||
If you want to run fixed command without confirmation you can use `-y` option:
 | 
			
		||||
 | 
			
		||||
```bash
 | 
			
		||||
alias fuck-it='export THEFUCK_REQUIRE_CONFIRMATION=False; fuck; export THEFUCK_REQUIRE_CONFIRMATION=True'
 | 
			
		||||
fuck -y
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
If you want to fix commands recursively until success you can use `-r` option:
 | 
			
		||||
 | 
			
		||||
```bash
 | 
			
		||||
fuck -r
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## Update
 | 
			
		||||
 | 
			
		||||
```bash
 | 
			
		||||
pip install --user thefuck --upgrade
 | 
			
		||||
pip install thefuck --upgrade
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
**Aliases changed in 1.34.**
 | 
			
		||||
@@ -188,6 +196,7 @@ using the matched rule and runs it. Rules enabled by default are as follows:
 | 
			
		||||
* `git_pull_uncommitted_changes` – stashes changes before pulling and pops them afterwards;
 | 
			
		||||
* `git_push` – adds `--set-upstream origin $branch` to previous failed `git push`;
 | 
			
		||||
* `git_push_pull` – runs `git pull` when `push` was rejected;
 | 
			
		||||
* `git_push_without_commits` – Creates an initial commit if you forget and only `git add .`, when setting up a new project;
 | 
			
		||||
* `git_rebase_no_changes` – runs `git rebase --skip` instead of `git rebase --continue` when there are no changes;
 | 
			
		||||
* `git_rm_local_modifications` –  adds `-f` or `--cached` when you try to `rm` a locally modified file;
 | 
			
		||||
* `git_rm_recursive` – adds `-r` when you try to `rm` a directory;
 | 
			
		||||
@@ -258,6 +267,7 @@ using the matched rule and runs it. Rules enabled by default are as follows:
 | 
			
		||||
* `workon_doesnt_exists` – fixes `virtualenvwrapper` env name os suggests to create new.
 | 
			
		||||
* `yarn_alias` – fixes aliased `yarn` commands like `yarn ls`;
 | 
			
		||||
* `yarn_command_not_found` – fixes misspelled `yarn` commands;
 | 
			
		||||
* `yarn_command_replaced` – fixes replaced `yarn` commands;
 | 
			
		||||
* `yarn_help` – makes it easier to open `yarn` documentation;
 | 
			
		||||
 | 
			
		||||
Enabled by default only on specific platforms:
 | 
			
		||||
@@ -296,7 +306,7 @@ side_effect(old_command: Command, fixed_command: str) -> None
 | 
			
		||||
```
 | 
			
		||||
and optional `enabled_by_default`, `requires_output` and `priority` variables.
 | 
			
		||||
 | 
			
		||||
`Command` has three attributes: `script`, `stdout`, `stderr` and `script_parts`.
 | 
			
		||||
`Command` has four attributes: `script`, `stdout`, `stderr` and `script_parts`.
 | 
			
		||||
Rule shouldn't change `Command`.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -387,6 +397,23 @@ export THEFUCK_PRIORITY='no_command=9999:apt_get=100'
 | 
			
		||||
export THEFUCK_HISTORY_LIMIT='2000'
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## Experimental instant mode
 | 
			
		||||
 | 
			
		||||
By default The Fuck reruns a previous command and that takes time,
 | 
			
		||||
in instant mode The Fuck logs output with [script](https://en.wikipedia.org/wiki/Script_(Unix))
 | 
			
		||||
and just reads the log.
 | 
			
		||||
 | 
			
		||||
[![gif with instant mode][instant-mode-gif-link]][instant-mode-gif-link]
 | 
			
		||||
 | 
			
		||||
At the moment only Python 3 with bash or zsh is supported.
 | 
			
		||||
 | 
			
		||||
For enabling instant mode you need to add `--enable-experimental-instant-mode`
 | 
			
		||||
to alias initialization in your `.bashrc`, `.bash_profile` or `.zshrc` like:
 | 
			
		||||
 | 
			
		||||
```bash
 | 
			
		||||
eval $(thefuck --alias --enable-experimental-instant-mode)
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## Developing
 | 
			
		||||
 | 
			
		||||
Install `The Fuck` for development:
 | 
			
		||||
@@ -427,12 +454,13 @@ 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/
 | 
			
		||||
[travis-badge]:    https://img.shields.io/travis/nvbn/thefuck.svg
 | 
			
		||||
[travis-badge]:    https://travis-ci.org/nvbn/thefuck.svg?branch=master
 | 
			
		||||
[travis-link]:     https://travis-ci.org/nvbn/thefuck
 | 
			
		||||
[appveyor-badge]:  https://img.shields.io/appveyor/ci/nvbn/thefuck.svg?label=windows%20build
 | 
			
		||||
[appveyor-badge]:  https://ci.appveyor.com/api/projects/status/1sskj4imj02um0gu/branch/master?svg=true
 | 
			
		||||
[appveyor-link]:   https://ci.appveyor.com/project/nvbn/thefuck
 | 
			
		||||
[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]:        http://brew.sh/
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								example_instant_mode.gif
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								example_instant_mode.gif
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 535 KiB  | 
							
								
								
									
										110
									
								
								fastentrypoints.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										110
									
								
								fastentrypoints.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,110 @@
 | 
			
		||||
# 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 = '''\
 | 
			
		||||
# -*- 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('\.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__)
 | 
			
		||||
							
								
								
									
										7
									
								
								setup.py
									
									
									
									
									
								
							
							
						
						
									
										7
									
								
								setup.py
									
									
									
									
									
								
							@@ -3,6 +3,8 @@ 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:
 | 
			
		||||
@@ -29,10 +31,11 @@ elif (3, 0) < version < (3, 3):
 | 
			
		||||
          ' ({}.{} detected).'.format(*version))
 | 
			
		||||
    sys.exit(-1)
 | 
			
		||||
 | 
			
		||||
VERSION = '3.15'
 | 
			
		||||
VERSION = '3.22'
 | 
			
		||||
 | 
			
		||||
install_requires = ['psutil', 'colorama', 'six', 'decorator']
 | 
			
		||||
install_requires = ['psutil', 'colorama', 'six', 'decorator', 'pyte']
 | 
			
		||||
extras_require = {':python_version<"3.4"': ['pathlib2'],
 | 
			
		||||
                  ':python_version<"3.3"': ['backports.shutil_get_terminal_size'],
 | 
			
		||||
                  ":sys_platform=='win32'": ['win_unicode_console']}
 | 
			
		||||
 | 
			
		||||
setup(name='thefuck',
 | 
			
		||||
 
 | 
			
		||||
@@ -1,3 +1,4 @@
 | 
			
		||||
import os
 | 
			
		||||
import pytest
 | 
			
		||||
from thefuck import shells
 | 
			
		||||
from thefuck import conf, const
 | 
			
		||||
@@ -7,7 +8,7 @@ shells.shell = shells.Generic()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def pytest_addoption(parser):
 | 
			
		||||
    """Adds `--run-without-docker` argument."""
 | 
			
		||||
    """Adds `--enable-functional` argument."""
 | 
			
		||||
    group = parser.getgroup("thefuck")
 | 
			
		||||
    group.addoption('--enable-functional', action="store_true", default=False,
 | 
			
		||||
                    help="Enable functional tests")
 | 
			
		||||
@@ -56,7 +57,13 @@ def set_shell(monkeypatch, request):
 | 
			
		||||
    def _set(cls):
 | 
			
		||||
        shell = cls()
 | 
			
		||||
        monkeypatch.setattr('thefuck.shells.shell', shell)
 | 
			
		||||
        request.addfinalizer()
 | 
			
		||||
        return shell
 | 
			
		||||
 | 
			
		||||
    return _set
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@pytest.fixture(autouse=True)
 | 
			
		||||
def os_environ(monkeypatch):
 | 
			
		||||
    env = {'PATH': os.environ['PATH']}
 | 
			
		||||
    monkeypatch.setattr('os.environ', env)
 | 
			
		||||
    return env
 | 
			
		||||
 
 | 
			
		||||
@@ -81,6 +81,5 @@ def without_confirmation(proc, TIMEOUT):
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def how_to_configure(proc, TIMEOUT):
 | 
			
		||||
    proc.sendline(u'unalias fuck')
 | 
			
		||||
    proc.sendline(u'fuck')
 | 
			
		||||
    assert proc.expect([TIMEOUT, u"alias isn't configured"])
 | 
			
		||||
 
 | 
			
		||||
@@ -48,4 +48,5 @@ def test_without_confirmation(proc, TIMEOUT):
 | 
			
		||||
 | 
			
		||||
@pytest.mark.functional
 | 
			
		||||
def test_how_to_configure_alias(proc, TIMEOUT):
 | 
			
		||||
    proc.sendline('unset -f fuck')
 | 
			
		||||
    how_to_configure(proc, TIMEOUT)
 | 
			
		||||
 
 | 
			
		||||
@@ -55,4 +55,5 @@ def test_without_confirmation(proc, TIMEOUT):
 | 
			
		||||
 | 
			
		||||
@pytest.mark.functional
 | 
			
		||||
def test_how_to_configure_alias(proc, TIMEOUT):
 | 
			
		||||
    proc.sendline(u'unfunction fuck')
 | 
			
		||||
    how_to_configure(proc, TIMEOUT)
 | 
			
		||||
 
 | 
			
		||||
@@ -39,7 +39,6 @@ parametrize_extensions = pytest.mark.parametrize('ext', tar_extensions)
 | 
			
		||||
# (filename as typed by the user, unquoted filename, quoted filename as per shells.quote)
 | 
			
		||||
parametrize_filename = pytest.mark.parametrize('filename, unquoted, quoted', [
 | 
			
		||||
    ('foo{}', 'foo{}', 'foo{}'),
 | 
			
		||||
    ('foo\ bar{}', 'foo bar{}', "'foo bar{}'"),
 | 
			
		||||
    ('"foo bar{}"', 'foo bar{}', "'foo bar{}'")])
 | 
			
		||||
 | 
			
		||||
parametrize_script = pytest.mark.parametrize('script, fixed', [
 | 
			
		||||
 
 | 
			
		||||
@@ -64,7 +64,6 @@ def test_side_effect(zip_error, script, filename):
 | 
			
		||||
@pytest.mark.parametrize('script,fixed,filename', [
 | 
			
		||||
    (u'unzip café', u"unzip café -d 'café'", u'café.zip'),
 | 
			
		||||
    (u'unzip foo', u'unzip foo -d foo', u'foo.zip'),
 | 
			
		||||
    (u"unzip foo\\ bar.zip", u"unzip foo\\ bar.zip -d 'foo bar'", u'foo.zip'),
 | 
			
		||||
    (u"unzip 'foo bar.zip'", u"unzip 'foo bar.zip' -d 'foo bar'", u'foo.zip'),
 | 
			
		||||
    (u'unzip foo.zip', u'unzip foo.zip -d foo', u'foo.zip')])
 | 
			
		||||
def test_get_new_command(zip_error, script, fixed, filename):
 | 
			
		||||
 
 | 
			
		||||
@@ -7,7 +7,7 @@ from tests.utils import Command
 | 
			
		||||
def git_not_command():
 | 
			
		||||
    return """git: 'brnch' is not a git command. See 'git --help'.
 | 
			
		||||
 | 
			
		||||
Did you mean this?
 | 
			
		||||
The most similar command is
 | 
			
		||||
branch
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
@@ -16,7 +16,7 @@ branch
 | 
			
		||||
def git_not_command_one_of_this():
 | 
			
		||||
    return """git: 'st' is not a git command. See 'git --help'.
 | 
			
		||||
 | 
			
		||||
Did you mean one of these?
 | 
			
		||||
The most similar commands are
 | 
			
		||||
status
 | 
			
		||||
reset
 | 
			
		||||
stage
 | 
			
		||||
@@ -29,7 +29,7 @@ stats
 | 
			
		||||
def git_not_command_closest():
 | 
			
		||||
    return '''git: 'tags' is not a git command. See 'git --help'.
 | 
			
		||||
 | 
			
		||||
Did you mean one of these?
 | 
			
		||||
The most similar commands are
 | 
			
		||||
\tstage
 | 
			
		||||
\ttag
 | 
			
		||||
'''
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										27
									
								
								tests/rules/test_git_push_without_commits.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								tests/rules/test_git_push_without_commits.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,27 @@
 | 
			
		||||
import pytest
 | 
			
		||||
 | 
			
		||||
from tests.utils import Command
 | 
			
		||||
from thefuck.rules.git_push_without_commits import (
 | 
			
		||||
    fix,
 | 
			
		||||
    get_new_command,
 | 
			
		||||
    match,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
command = 'git push -u origin master'
 | 
			
		||||
expected_error = '''
 | 
			
		||||
error: src refspec master does not match any.
 | 
			
		||||
error: failed to push some refs to 'git@github.com:User/repo.git'
 | 
			
		||||
'''
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@pytest.mark.parametrize('command', [Command(command, stderr=expected_error)])
 | 
			
		||||
def test_match(command):
 | 
			
		||||
    assert match(command)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@pytest.mark.parametrize('command, result', [(
 | 
			
		||||
    Command(command, stderr=expected_error),
 | 
			
		||||
    fix.format(command=command),
 | 
			
		||||
)])
 | 
			
		||||
def test_get_new_command(command, result):
 | 
			
		||||
    assert get_new_command(command) == result
 | 
			
		||||
@@ -15,4 +15,4 @@ def test_match(stderr):
 | 
			
		||||
 | 
			
		||||
def test_get_new_command(stderr):
 | 
			
		||||
    assert (get_new_command(Command('git stash pop', stderr=stderr))
 | 
			
		||||
            == "git add . && git stash pop && git reset .")
 | 
			
		||||
            == "git add --update && git stash pop && git reset .")
 | 
			
		||||
 
 | 
			
		||||
@@ -1,34 +1,31 @@
 | 
			
		||||
# -*- coding: utf-8 -*-
 | 
			
		||||
 | 
			
		||||
import pytest
 | 
			
		||||
from tests.utils import Command
 | 
			
		||||
from thefuck.rules.heroku_not_command import match, get_new_command
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def suggest_stderr(cmd):
 | 
			
		||||
    return ''' !    `{}` is not a heroku command.
 | 
			
		||||
     !    Perhaps you meant `logs`, `pg`.
 | 
			
		||||
     !    See `heroku help` for a list of available commands.'''.format(cmd)
 | 
			
		||||
suggest_stderr = '''
 | 
			
		||||
 ▸    log is not a heroku command.
 | 
			
		||||
 ▸    Perhaps you meant logs?
 | 
			
		||||
 ▸    Run heroku _ to run heroku logs.
 | 
			
		||||
 ▸    Run heroku help for a list of available commands.'''
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
no_suggest_stderr = ''' !    `aaaaa` is not a heroku command.
 | 
			
		||||
 !    See `heroku help` for a list of available commands.'''
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@pytest.mark.parametrize('cmd', ['log', 'pge'])
 | 
			
		||||
@pytest.mark.parametrize('cmd', ['log'])
 | 
			
		||||
def test_match(cmd):
 | 
			
		||||
    assert match(
 | 
			
		||||
        Command('heroku {}'.format(cmd), stderr=suggest_stderr(cmd)))
 | 
			
		||||
        Command('heroku {}'.format(cmd), stderr=suggest_stderr))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@pytest.mark.parametrize('script, stderr', [
 | 
			
		||||
    ('cat log', suggest_stderr('log')),
 | 
			
		||||
    ('heroku aaa', no_suggest_stderr)])
 | 
			
		||||
    ('cat log', suggest_stderr)])
 | 
			
		||||
def test_not_match(script, stderr):
 | 
			
		||||
    assert not match(Command(script, stderr=stderr))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@pytest.mark.parametrize('cmd, result', [
 | 
			
		||||
    ('log', ['heroku logs', 'heroku pg']),
 | 
			
		||||
    ('pge', ['heroku pg', 'heroku logs'])])
 | 
			
		||||
    ('log', 'heroku logs')])
 | 
			
		||||
def test_get_new_command(cmd, result):
 | 
			
		||||
    command = Command('heroku {}'.format(cmd), stderr=suggest_stderr(cmd))
 | 
			
		||||
    command = Command('heroku {}'.format(cmd), stderr=suggest_stderr)
 | 
			
		||||
    assert get_new_command(command) == result
 | 
			
		||||
 
 | 
			
		||||
@@ -4,12 +4,6 @@ from thefuck.rules.missing_space_before_subcommand import (
 | 
			
		||||
from tests.utils import Command
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@pytest.fixture(autouse=True)
 | 
			
		||||
def which(mocker):
 | 
			
		||||
    return mocker.patch('thefuck.rules.missing_space_before_subcommand.which',
 | 
			
		||||
                        return_value=None)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@pytest.fixture(autouse=True)
 | 
			
		||||
def all_executables(mocker):
 | 
			
		||||
    return mocker.patch(
 | 
			
		||||
@@ -23,11 +17,8 @@ def test_match(script):
 | 
			
		||||
    assert match(Command(script))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@pytest.mark.parametrize('script, which_result', [
 | 
			
		||||
    ('git branch', '/usr/bin/git'),
 | 
			
		||||
    ('vimfile', None)])
 | 
			
		||||
def test_not_match(script, which_result, which):
 | 
			
		||||
    which.return_value = which_result
 | 
			
		||||
@pytest.mark.parametrize('script', ['git branch', 'vimfile'])
 | 
			
		||||
def test_not_match(script):
 | 
			
		||||
    assert not match(Command(script))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -4,12 +4,13 @@ from tests.utils import Command
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
stderr_remove = 'error Did you mean `yarn remove`?'
 | 
			
		||||
 | 
			
		||||
stderr_etl = 'error Command "etil" not found. Did you mean "etl"?'
 | 
			
		||||
stderr_list = 'error Did you mean `yarn list`?'
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@pytest.mark.parametrize('command', [
 | 
			
		||||
    Command(script='yarn rm', stderr=stderr_remove),
 | 
			
		||||
    Command(script='yarn etil', stderr=stderr_etl),
 | 
			
		||||
    Command(script='yarn ls', stderr=stderr_list)])
 | 
			
		||||
def test_match(command):
 | 
			
		||||
    assert match(command)
 | 
			
		||||
@@ -17,6 +18,7 @@ def test_match(command):
 | 
			
		||||
 | 
			
		||||
@pytest.mark.parametrize('command, new_command', [
 | 
			
		||||
    (Command('yarn rm', stderr=stderr_remove), 'yarn remove'),
 | 
			
		||||
    (Command('yarn etil', stderr=stderr_etl), 'yarn etl'),
 | 
			
		||||
    (Command('yarn ls', stderr=stderr_list), 'yarn list')])
 | 
			
		||||
def test_get_new_command(command, new_command):
 | 
			
		||||
    assert get_new_command(command) == new_command
 | 
			
		||||
 
 | 
			
		||||
@@ -106,6 +106,13 @@ def test_not_match(command):
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@pytest.mark.parametrize('command, result', [
 | 
			
		||||
    (Command('yarn whyy webpack', stderr=stderr('whyy')), 'yarn why webpack')])
 | 
			
		||||
    (Command('yarn whyy webpack', stderr=stderr('whyy')),
 | 
			
		||||
     'yarn why webpack'),
 | 
			
		||||
    (Command('yarn require lodash', stderr=stderr('require')),
 | 
			
		||||
     'yarn add lodash')])
 | 
			
		||||
def test_get_new_command(command, result):
 | 
			
		||||
    assert get_new_command(command)[0] == result
 | 
			
		||||
    fixed_command = get_new_command(command)
 | 
			
		||||
    if isinstance(fixed_command, list):
 | 
			
		||||
        fixed_command = fixed_command[0]
 | 
			
		||||
 | 
			
		||||
    assert fixed_command == result
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										32
									
								
								tests/rules/test_yarn_command_replaced.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								tests/rules/test_yarn_command_replaced.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,32 @@
 | 
			
		||||
import pytest
 | 
			
		||||
from tests.utils import Command
 | 
			
		||||
from thefuck.rules.yarn_command_replaced import match, get_new_command
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
stderr = ('error `install` has been replaced with `add` to add new '
 | 
			
		||||
          'dependencies. Run "yarn add {}" instead.').format
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@pytest.mark.parametrize('command', [
 | 
			
		||||
    Command(script='yarn install redux', stderr=stderr('redux')),
 | 
			
		||||
    Command(script='yarn install moment', stderr=stderr('moment')),
 | 
			
		||||
    Command(script='yarn install lodash', stderr=stderr('lodash'))])
 | 
			
		||||
def test_match(command):
 | 
			
		||||
    assert match(command)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@pytest.mark.parametrize('command', [
 | 
			
		||||
    Command('yarn install')])
 | 
			
		||||
def test_not_match(command):
 | 
			
		||||
    assert not match(command)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@pytest.mark.parametrize('command, new_command', [
 | 
			
		||||
    (Command('yarn install redux', stderr=stderr('redux')),
 | 
			
		||||
     'yarn add redux'),
 | 
			
		||||
    (Command('yarn install moment', stderr=stderr('moment')),
 | 
			
		||||
     'yarn add moment'),
 | 
			
		||||
    (Command('yarn install lodash', stderr=stderr('lodash')),
 | 
			
		||||
     'yarn add lodash')])
 | 
			
		||||
def test_get_new_command(command, new_command):
 | 
			
		||||
    assert get_new_command(command) == new_command
 | 
			
		||||
@@ -50,8 +50,8 @@ def test_match(command):
 | 
			
		||||
    assert match(command)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@pytest.mark.parametrize('command, new_command', [
 | 
			
		||||
@pytest.mark.parametrize('command, url', [
 | 
			
		||||
    (Command('yarn help clean', stdout=stdout_clean),
 | 
			
		||||
     open_command('https://yarnpkg.com/en/docs/cli/clean'))])
 | 
			
		||||
def test_get_new_command(command, new_command):
 | 
			
		||||
    assert get_new_command(command) == new_command
 | 
			
		||||
     'https://yarnpkg.com/en/docs/cli/clean')])
 | 
			
		||||
def test_get_new_command(command, url):
 | 
			
		||||
    assert get_new_command(command) == open_command(url)
 | 
			
		||||
 
 | 
			
		||||
@@ -33,6 +33,9 @@ class TestBash(object):
 | 
			
		||||
    def test_and_(self, shell):
 | 
			
		||||
        assert shell.and_('ls', 'cd') == 'ls && cd'
 | 
			
		||||
 | 
			
		||||
    def test_or_(self, shell):
 | 
			
		||||
        assert shell.or_('ls', 'cd') == 'ls || cd'
 | 
			
		||||
 | 
			
		||||
    def test_get_aliases(self, shell):
 | 
			
		||||
        assert shell.get_aliases() == {'fuck': 'eval $(thefuck $(fc -ln -1))',
 | 
			
		||||
                                       'l': 'ls -CF',
 | 
			
		||||
@@ -40,18 +43,17 @@ class TestBash(object):
 | 
			
		||||
                                       'll': 'ls -alF'}
 | 
			
		||||
 | 
			
		||||
    def test_app_alias(self, shell):
 | 
			
		||||
        assert 'alias fuck' in shell.app_alias('fuck')
 | 
			
		||||
        assert 'alias FUCK' in shell.app_alias('FUCK')
 | 
			
		||||
        assert 'fuck () {' in shell.app_alias('fuck')
 | 
			
		||||
        assert 'FUCK () {' in shell.app_alias('FUCK')
 | 
			
		||||
        assert 'thefuck' in shell.app_alias('fuck')
 | 
			
		||||
        assert 'TF_ALIAS=fuck' in shell.app_alias('fuck')
 | 
			
		||||
        assert 'PYTHONIOENCODING=utf-8' in shell.app_alias('fuck')
 | 
			
		||||
        assert 'PYTHONIOENCODING' in shell.app_alias('fuck')
 | 
			
		||||
 | 
			
		||||
    def test_app_alias_variables_correctly_set(self, shell):
 | 
			
		||||
        alias = shell.app_alias('fuck')
 | 
			
		||||
        assert "alias fuck='TF_CMD=$(TF_ALIAS" in alias
 | 
			
		||||
        assert '$(TF_ALIAS=fuck PYTHONIOENCODING' in alias
 | 
			
		||||
        assert 'PYTHONIOENCODING=utf-8 TF_SHELL_ALIASES' in alias
 | 
			
		||||
        assert 'ALIASES=$(alias) thefuck' in alias
 | 
			
		||||
        assert "fuck () {" in alias
 | 
			
		||||
        assert "TF_ALIAS=fuck" in alias
 | 
			
		||||
        assert 'PYTHONIOENCODING=utf-8' in alias
 | 
			
		||||
        assert 'TF_SHELL_ALIASES=$(alias)' in alias
 | 
			
		||||
 | 
			
		||||
    def test_get_history(self, history_lines, shell):
 | 
			
		||||
        history_lines(['ls', 'rm'])
 | 
			
		||||
 
 | 
			
		||||
@@ -18,17 +18,14 @@ class TestFish(object):
 | 
			
		||||
            b'man\nmath\npopd\npushd\nruby')
 | 
			
		||||
        return mock
 | 
			
		||||
 | 
			
		||||
    @pytest.fixture
 | 
			
		||||
    def os_environ(self, monkeypatch, key, value):
 | 
			
		||||
        monkeypatch.setattr('os.environ', {key: value})
 | 
			
		||||
 | 
			
		||||
    @pytest.mark.parametrize('key, value', [
 | 
			
		||||
        ('TF_OVERRIDDEN_ALIASES', 'cut,git,sed'),  # legacy
 | 
			
		||||
        ('THEFUCK_OVERRIDDEN_ALIASES', 'cut,git,sed'),
 | 
			
		||||
        ('THEFUCK_OVERRIDDEN_ALIASES', 'cut, git, sed'),
 | 
			
		||||
        ('THEFUCK_OVERRIDDEN_ALIASES', ' cut,\tgit,sed\n'),
 | 
			
		||||
        ('THEFUCK_OVERRIDDEN_ALIASES', '\ncut,\n\ngit,\tsed\r')])
 | 
			
		||||
    def test_get_overridden_aliases(self, shell, os_environ):
 | 
			
		||||
    def test_get_overridden_aliases(self, shell, os_environ, key, value):
 | 
			
		||||
        os_environ[key] = value
 | 
			
		||||
        assert shell._get_overridden_aliases() == {'cd', 'cut', 'git', 'grep',
 | 
			
		||||
                                                   'ls', 'man', 'open', 'sed'}
 | 
			
		||||
 | 
			
		||||
@@ -55,6 +52,9 @@ class TestFish(object):
 | 
			
		||||
    def test_and_(self, shell):
 | 
			
		||||
        assert shell.and_('foo', 'bar') == 'foo; and bar'
 | 
			
		||||
 | 
			
		||||
    def test_or_(self, shell):
 | 
			
		||||
        assert shell.or_('foo', 'bar') == 'foo; or bar'
 | 
			
		||||
 | 
			
		||||
    def test_get_aliases(self, shell):
 | 
			
		||||
        assert shell.get_aliases() == {'fish_config': 'fish_config',
 | 
			
		||||
                                       'fuck': 'fuck',
 | 
			
		||||
 
 | 
			
		||||
@@ -18,6 +18,9 @@ class TestGeneric(object):
 | 
			
		||||
    def test_and_(self, shell):
 | 
			
		||||
        assert shell.and_('ls', 'cd') == 'ls && cd'
 | 
			
		||||
 | 
			
		||||
    def test_or_(self, shell):
 | 
			
		||||
        assert shell.or_('ls', 'cd') == 'ls || cd'
 | 
			
		||||
 | 
			
		||||
    def test_get_aliases(self, shell):
 | 
			
		||||
        assert shell.get_aliases() == {}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -34,6 +34,9 @@ class TestTcsh(object):
 | 
			
		||||
    def test_and_(self, shell):
 | 
			
		||||
        assert shell.and_('ls', 'cd') == 'ls && cd'
 | 
			
		||||
 | 
			
		||||
    def test_or_(self, shell):
 | 
			
		||||
        assert shell.or_('ls', 'cd') == 'ls || cd'
 | 
			
		||||
 | 
			
		||||
    def test_get_aliases(self, shell):
 | 
			
		||||
        assert shell.get_aliases() == {'fuck': 'eval $(thefuck $(fc -ln -1))',
 | 
			
		||||
                                       'l': 'ls -CF',
 | 
			
		||||
 
 | 
			
		||||
@@ -32,6 +32,9 @@ class TestZsh(object):
 | 
			
		||||
    def test_and_(self, shell):
 | 
			
		||||
        assert shell.and_('ls', 'cd') == 'ls && cd'
 | 
			
		||||
 | 
			
		||||
    def test_or_(self, shell):
 | 
			
		||||
        assert shell.or_('ls', 'cd') == 'ls || cd'
 | 
			
		||||
 | 
			
		||||
    def test_get_aliases(self, shell):
 | 
			
		||||
        assert shell.get_aliases() == {
 | 
			
		||||
            'fuck': 'eval $(thefuck $(fc -ln -1 | tail -n 1))',
 | 
			
		||||
@@ -40,17 +43,17 @@ class TestZsh(object):
 | 
			
		||||
            'll': 'ls -alF'}
 | 
			
		||||
 | 
			
		||||
    def test_app_alias(self, shell):
 | 
			
		||||
        assert 'alias fuck' in shell.app_alias('fuck')
 | 
			
		||||
        assert 'alias FUCK' in shell.app_alias('FUCK')
 | 
			
		||||
        assert 'fuck () {' in shell.app_alias('fuck')
 | 
			
		||||
        assert 'FUCK () {' in shell.app_alias('FUCK')
 | 
			
		||||
        assert 'thefuck' in shell.app_alias('fuck')
 | 
			
		||||
        assert 'PYTHONIOENCODING' in shell.app_alias('fuck')
 | 
			
		||||
 | 
			
		||||
    def test_app_alias_variables_correctly_set(self, shell):
 | 
			
		||||
        alias = shell.app_alias('fuck')
 | 
			
		||||
        assert "alias fuck='TF_CMD=$(TF_ALIAS" in alias
 | 
			
		||||
        assert '$(TF_ALIAS=fuck PYTHONIOENCODING' in alias
 | 
			
		||||
        assert 'PYTHONIOENCODING=utf-8 TF_SHELL_ALIASES' in alias
 | 
			
		||||
        assert 'ALIASES=$(alias) thefuck' in alias
 | 
			
		||||
        assert "fuck () {" in alias
 | 
			
		||||
        assert "TF_ALIAS=fuck" in alias
 | 
			
		||||
        assert 'PYTHONIOENCODING=utf-8' in alias
 | 
			
		||||
        assert 'TF_SHELL_ALIASES=$(alias)' in alias
 | 
			
		||||
 | 
			
		||||
    def test_get_history(self, history_lines, shell):
 | 
			
		||||
        history_lines([': 1432613911:0;ls', ': 1432613916:0;rm'])
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										32
									
								
								tests/test_argument_parser.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								tests/test_argument_parser.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,32 @@
 | 
			
		||||
import pytest
 | 
			
		||||
from thefuck.argument_parser import Parser
 | 
			
		||||
from thefuck.const import ARGUMENT_PLACEHOLDER
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def _args(**override):
 | 
			
		||||
    args = {'alias': None, 'command': [], 'yes': False,
 | 
			
		||||
            'help': False, 'version': False, 'debug': False,
 | 
			
		||||
            'force_command': None, 'repeat': False,
 | 
			
		||||
            'enable_experimental_instant_mode': False}
 | 
			
		||||
    args.update(override)
 | 
			
		||||
    return args
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@pytest.mark.parametrize('argv, result', [
 | 
			
		||||
    (['thefuck'], _args()),
 | 
			
		||||
    (['thefuck', '-a'], _args(alias='fuck')),
 | 
			
		||||
    (['thefuck', '--alias', '--enable-experimental-instant-mode'],
 | 
			
		||||
     _args(alias='fuck', enable_experimental_instant_mode=True)),
 | 
			
		||||
    (['thefuck', '-a', 'fix'], _args(alias='fix')),
 | 
			
		||||
    (['thefuck', 'git', 'branch', ARGUMENT_PLACEHOLDER, '-y'],
 | 
			
		||||
     _args(command=['git', 'branch'], yes=True)),
 | 
			
		||||
    (['thefuck', 'git', 'branch', '-a', ARGUMENT_PLACEHOLDER, '-y'],
 | 
			
		||||
     _args(command=['git', 'branch', '-a'], yes=True)),
 | 
			
		||||
    (['thefuck', ARGUMENT_PLACEHOLDER, '-v'], _args(version=True)),
 | 
			
		||||
    (['thefuck', ARGUMENT_PLACEHOLDER, '--help'], _args(help=True)),
 | 
			
		||||
    (['thefuck', 'git', 'branch', '-a', ARGUMENT_PLACEHOLDER, '-y', '-d'],
 | 
			
		||||
     _args(command=['git', 'branch', '-a'], yes=True, debug=True)),
 | 
			
		||||
    (['thefuck', 'git', 'branch', '-a', ARGUMENT_PLACEHOLDER, '-r', '-d'],
 | 
			
		||||
     _args(command=['git', 'branch', '-a'], repeat=True, debug=True))])
 | 
			
		||||
def test_parse(argv, result):
 | 
			
		||||
    assert vars(Parser().parse(argv)) == result
 | 
			
		||||
@@ -10,14 +10,6 @@ def load_source(mocker):
 | 
			
		||||
    return mocker.patch('thefuck.conf.load_source')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@pytest.fixture
 | 
			
		||||
def environ(monkeypatch):
 | 
			
		||||
    data = {}
 | 
			
		||||
    monkeypatch.setattr('thefuck.conf.os.environ', data)
 | 
			
		||||
    return data
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@pytest.mark.usefixture('environ')
 | 
			
		||||
def test_settings_defaults(load_source, settings):
 | 
			
		||||
    load_source.return_value = object()
 | 
			
		||||
    settings.init()
 | 
			
		||||
@@ -25,7 +17,6 @@ def test_settings_defaults(load_source, settings):
 | 
			
		||||
        assert getattr(settings, key) == val
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@pytest.mark.usefixture('environ')
 | 
			
		||||
class TestSettingsFromFile(object):
 | 
			
		||||
    def test_from_file(self, load_source, settings):
 | 
			
		||||
        load_source.return_value = Mock(rules=['test'],
 | 
			
		||||
@@ -54,15 +45,15 @@ class TestSettingsFromFile(object):
 | 
			
		||||
 | 
			
		||||
@pytest.mark.usefixture('load_source')
 | 
			
		||||
class TestSettingsFromEnv(object):
 | 
			
		||||
    def test_from_env(self, environ, settings):
 | 
			
		||||
        environ.update({'THEFUCK_RULES': 'bash:lisp',
 | 
			
		||||
                        'THEFUCK_EXCLUDE_RULES': 'git:vim',
 | 
			
		||||
                        'THEFUCK_WAIT_COMMAND': '55',
 | 
			
		||||
                        'THEFUCK_REQUIRE_CONFIRMATION': 'true',
 | 
			
		||||
                        'THEFUCK_NO_COLORS': 'false',
 | 
			
		||||
                        'THEFUCK_PRIORITY': 'bash=10:lisp=wrong:vim=15',
 | 
			
		||||
                        'THEFUCK_WAIT_SLOW_COMMAND': '999',
 | 
			
		||||
                        'THEFUCK_SLOW_COMMANDS': 'lein:react-native:./gradlew'})
 | 
			
		||||
    def test_from_env(self, os_environ, settings):
 | 
			
		||||
        os_environ.update({'THEFUCK_RULES': 'bash:lisp',
 | 
			
		||||
                           'THEFUCK_EXCLUDE_RULES': 'git:vim',
 | 
			
		||||
                           'THEFUCK_WAIT_COMMAND': '55',
 | 
			
		||||
                           'THEFUCK_REQUIRE_CONFIRMATION': 'true',
 | 
			
		||||
                           'THEFUCK_NO_COLORS': 'false',
 | 
			
		||||
                           'THEFUCK_PRIORITY': 'bash=10:lisp=wrong:vim=15',
 | 
			
		||||
                           'THEFUCK_WAIT_SLOW_COMMAND': '999',
 | 
			
		||||
                           'THEFUCK_SLOW_COMMANDS': 'lein:react-native:./gradlew'})
 | 
			
		||||
        settings.init()
 | 
			
		||||
        assert settings.rules == ['bash', 'lisp']
 | 
			
		||||
        assert settings.exclude_rules == ['git', 'vim']
 | 
			
		||||
@@ -73,12 +64,19 @@ class TestSettingsFromEnv(object):
 | 
			
		||||
        assert settings.wait_slow_command == 999
 | 
			
		||||
        assert settings.slow_commands == ['lein', 'react-native', './gradlew']
 | 
			
		||||
 | 
			
		||||
    def test_from_env_with_DEFAULT(self, environ, settings):
 | 
			
		||||
        environ.update({'THEFUCK_RULES': 'DEFAULT_RULES:bash:lisp'})
 | 
			
		||||
    def test_from_env_with_DEFAULT(self, os_environ, settings):
 | 
			
		||||
        os_environ.update({'THEFUCK_RULES': 'DEFAULT_RULES:bash:lisp'})
 | 
			
		||||
        settings.init()
 | 
			
		||||
        assert settings.rules == const.DEFAULT_RULES + ['bash', 'lisp']
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_settings_from_args(settings):
 | 
			
		||||
    settings.init(Mock(yes=True, debug=True, repeat=True))
 | 
			
		||||
    assert not settings.require_confirmation
 | 
			
		||||
    assert settings.debug
 | 
			
		||||
    assert settings.repeat
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TestInitializeSettingsFile(object):
 | 
			
		||||
    def test_ignore_if_exists(self, settings):
 | 
			
		||||
        settings_path_mock = Mock(is_file=Mock(return_value=True), open=Mock())
 | 
			
		||||
@@ -109,15 +107,15 @@ class TestInitializeSettingsFile(object):
 | 
			
		||||
    (False, '/user/test/config/', '/user/test/config/thefuck'),
 | 
			
		||||
    (True, '~/.config', '~/.thefuck'),
 | 
			
		||||
    (True, '/user/test/config/', '~/.thefuck')])
 | 
			
		||||
def test_get_user_dir_path(mocker, environ, settings, legacy_dir_exists,
 | 
			
		||||
def test_get_user_dir_path(mocker, os_environ, settings, legacy_dir_exists,
 | 
			
		||||
                           xdg_config_home, result):
 | 
			
		||||
    mocker.patch('thefuck.conf.Path.is_dir',
 | 
			
		||||
                 return_value=legacy_dir_exists)
 | 
			
		||||
 | 
			
		||||
    if xdg_config_home is not None:
 | 
			
		||||
        environ['XDG_CONFIG_HOME'] = xdg_config_home
 | 
			
		||||
        os_environ['XDG_CONFIG_HOME'] = xdg_config_home
 | 
			
		||||
    else:
 | 
			
		||||
        environ.pop('XDG_CONFIG_HOME', None)
 | 
			
		||||
        os_environ.pop('XDG_CONFIG_HOME', None)
 | 
			
		||||
 | 
			
		||||
    path = settings._get_user_dir_path().as_posix()
 | 
			
		||||
    assert path == os.path.expanduser(result)
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,6 @@
 | 
			
		||||
import pytest
 | 
			
		||||
import json
 | 
			
		||||
from six import StringIO
 | 
			
		||||
from mock import MagicMock
 | 
			
		||||
from thefuck.shells.generic import ShellConfiguration
 | 
			
		||||
from thefuck.not_configured import main
 | 
			
		||||
@@ -11,19 +13,33 @@ def usage_tracker(mocker):
 | 
			
		||||
        new_callable=MagicMock)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def _assert_tracker_updated(usage_tracker, pid):
 | 
			
		||||
@pytest.fixture(autouse=True)
 | 
			
		||||
def usage_tracker_io(usage_tracker):
 | 
			
		||||
    io = StringIO()
 | 
			
		||||
    usage_tracker.return_value \
 | 
			
		||||
        .open.return_value \
 | 
			
		||||
        .__enter__.return_value \
 | 
			
		||||
        .write.assert_called_once_with(str(pid))
 | 
			
		||||
                 .open.return_value \
 | 
			
		||||
                 .__enter__.return_value = io
 | 
			
		||||
    return io
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def _change_tracker(usage_tracker, pid):
 | 
			
		||||
    usage_tracker.return_value.exists.return_value = True
 | 
			
		||||
@pytest.fixture(autouse=True)
 | 
			
		||||
def usage_tracker_exists(usage_tracker):
 | 
			
		||||
    usage_tracker.return_value \
 | 
			
		||||
        .open.return_value \
 | 
			
		||||
        .__enter__.return_value \
 | 
			
		||||
        .read.return_value = str(pid)
 | 
			
		||||
                 .exists.return_value = True
 | 
			
		||||
    return usage_tracker.return_value.exists
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def _assert_tracker_updated(usage_tracker_io, pid):
 | 
			
		||||
    usage_tracker_io.seek(0)
 | 
			
		||||
    info = json.load(usage_tracker_io)
 | 
			
		||||
    assert info['pid'] == pid
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def _change_tracker(usage_tracker_io, pid):
 | 
			
		||||
    usage_tracker_io.truncate(0)
 | 
			
		||||
    info = {'pid': pid, 'time': 0}
 | 
			
		||||
    json.dump(info, usage_tracker_io)
 | 
			
		||||
    usage_tracker_io.seek(0)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@pytest.fixture(autouse=True)
 | 
			
		||||
@@ -67,29 +83,28 @@ def test_for_generic_shell(shell, logs):
 | 
			
		||||
    logs.how_to_configure_alias.assert_called_once()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_on_first_run(usage_tracker, shell_pid, logs):
 | 
			
		||||
def test_on_first_run(usage_tracker_io, usage_tracker_exists, shell_pid, logs):
 | 
			
		||||
    shell_pid.return_value = 12
 | 
			
		||||
    usage_tracker.return_value.exists.return_value = False
 | 
			
		||||
    main()
 | 
			
		||||
    _assert_tracker_updated(usage_tracker, 12)
 | 
			
		||||
    usage_tracker_exists.return_value = False
 | 
			
		||||
    _assert_tracker_updated(usage_tracker_io, 12)
 | 
			
		||||
    logs.how_to_configure_alias.assert_called_once()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_on_run_after_other_commands(usage_tracker, shell_pid, shell, logs):
 | 
			
		||||
def test_on_run_after_other_commands(usage_tracker_io, shell_pid, shell, logs):
 | 
			
		||||
    shell_pid.return_value = 12
 | 
			
		||||
    shell.get_history.return_value = ['fuck', 'ls']
 | 
			
		||||
    _change_tracker(usage_tracker, 12)
 | 
			
		||||
    _change_tracker(usage_tracker_io, 12)
 | 
			
		||||
    main()
 | 
			
		||||
    logs.how_to_configure_alias.assert_called_once()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_on_first_run_from_current_shell(usage_tracker, shell_pid,
 | 
			
		||||
def test_on_first_run_from_current_shell(usage_tracker_io, shell_pid,
 | 
			
		||||
                                         shell, logs):
 | 
			
		||||
    shell.get_history.return_value = ['fuck']
 | 
			
		||||
    shell_pid.return_value = 12
 | 
			
		||||
    _change_tracker(usage_tracker, 55)
 | 
			
		||||
    main()
 | 
			
		||||
    _assert_tracker_updated(usage_tracker, 12)
 | 
			
		||||
    _assert_tracker_updated(usage_tracker_io, 12)
 | 
			
		||||
    logs.how_to_configure_alias.assert_called_once()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -104,21 +119,21 @@ def test_when_cant_configure_automatically(shell_pid, shell, logs):
 | 
			
		||||
    logs.how_to_configure_alias.assert_called_once()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_when_already_configured(usage_tracker, shell_pid,
 | 
			
		||||
def test_when_already_configured(usage_tracker_io, shell_pid,
 | 
			
		||||
                                 shell, shell_config, logs):
 | 
			
		||||
    shell.get_history.return_value = ['fuck']
 | 
			
		||||
    shell_pid.return_value = 12
 | 
			
		||||
    _change_tracker(usage_tracker, 12)
 | 
			
		||||
    _change_tracker(usage_tracker_io, 12)
 | 
			
		||||
    shell_config.read.return_value = 'eval $(thefuck --alias)'
 | 
			
		||||
    main()
 | 
			
		||||
    logs.already_configured.assert_called_once()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_when_successfuly_configured(usage_tracker, shell_pid,
 | 
			
		||||
                                     shell, shell_config, logs):
 | 
			
		||||
def test_when_successfully_configured(usage_tracker_io, shell_pid,
 | 
			
		||||
                                      shell, shell_config, logs):
 | 
			
		||||
    shell.get_history.return_value = ['fuck']
 | 
			
		||||
    shell_pid.return_value = 12
 | 
			
		||||
    _change_tracker(usage_tracker, 12)
 | 
			
		||||
    _change_tracker(usage_tracker_io, 12)
 | 
			
		||||
    shell_config.read.return_value = ''
 | 
			
		||||
    main()
 | 
			
		||||
    shell_config.write.assert_any_call('eval $(thefuck --alias)')
 | 
			
		||||
 
 | 
			
		||||
@@ -28,6 +28,20 @@ class TestCorrectedCommand(object):
 | 
			
		||||
        assert u'{}'.format(CorrectedCommand(u'echo café', None, 100)) == \
 | 
			
		||||
               u'CorrectedCommand(script=echo café, side_effect=None, priority=100)'
 | 
			
		||||
 | 
			
		||||
    @pytest.mark.parametrize('script, printed, override_settings', [
 | 
			
		||||
        ('git branch', 'git branch', {'repeat': False, 'debug': False}),
 | 
			
		||||
        ('git brunch',
 | 
			
		||||
         "git brunch || fuck --repeat --force-command 'git brunch'",
 | 
			
		||||
         {'repeat': True, 'debug': False}),
 | 
			
		||||
        ('git brunch',
 | 
			
		||||
         "git brunch || fuck --repeat --debug --force-command 'git brunch'",
 | 
			
		||||
         {'repeat': True, 'debug': True})])
 | 
			
		||||
    def test_run(self, capsys, settings, script, printed, override_settings):
 | 
			
		||||
        settings.update(override_settings)
 | 
			
		||||
        CorrectedCommand(script, None, 1000).run(Command())
 | 
			
		||||
        out, _ = capsys.readouterr()
 | 
			
		||||
        assert out[:-1] == printed
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TestRule(object):
 | 
			
		||||
    def test_from_path(self, mocker):
 | 
			
		||||
@@ -96,16 +110,15 @@ class TestCommand(object):
 | 
			
		||||
        Popen = Mock()
 | 
			
		||||
        Popen.return_value.stdout.read.return_value = b'stdout'
 | 
			
		||||
        Popen.return_value.stderr.read.return_value = b'stderr'
 | 
			
		||||
        monkeypatch.setattr('thefuck.types.Popen', Popen)
 | 
			
		||||
        monkeypatch.setattr('thefuck.output_readers.rerun.Popen', Popen)
 | 
			
		||||
        return Popen
 | 
			
		||||
 | 
			
		||||
    @pytest.fixture(autouse=True)
 | 
			
		||||
    def prepare(self, monkeypatch):
 | 
			
		||||
        monkeypatch.setattr('thefuck.types.os.environ', {})
 | 
			
		||||
        monkeypatch.setattr('thefuck.types.Command._wait_output',
 | 
			
		||||
                            staticmethod(lambda *_: True))
 | 
			
		||||
        monkeypatch.setattr('thefuck.output_readers.rerun._wait_output',
 | 
			
		||||
                            lambda *_: True)
 | 
			
		||||
 | 
			
		||||
    def test_from_script_calls(self, Popen, settings):
 | 
			
		||||
    def test_from_script_calls(self, Popen, settings, os_environ):
 | 
			
		||||
        settings.env = {}
 | 
			
		||||
        assert Command.from_raw_script(
 | 
			
		||||
            ['apt-get', 'search', 'vim']) == Command(
 | 
			
		||||
@@ -115,7 +128,7 @@ class TestCommand(object):
 | 
			
		||||
                                      stdin=PIPE,
 | 
			
		||||
                                      stdout=PIPE,
 | 
			
		||||
                                      stderr=PIPE,
 | 
			
		||||
                                      env={})
 | 
			
		||||
                                      env=os_environ)
 | 
			
		||||
 | 
			
		||||
    @pytest.mark.parametrize('script, result', [
 | 
			
		||||
        ([''], None),
 | 
			
		||||
 
 | 
			
		||||
@@ -69,34 +69,40 @@ class TestSelectCommand(object):
 | 
			
		||||
    def test_without_confirmation(self, capsys, commands, settings):
 | 
			
		||||
        settings.require_confirmation = False
 | 
			
		||||
        assert ui.select_command(iter(commands)) == commands[0]
 | 
			
		||||
        assert capsys.readouterr() == ('', 'ls\n')
 | 
			
		||||
        assert capsys.readouterr() == ('', const.USER_COMMAND_MARK + 'ls\n')
 | 
			
		||||
 | 
			
		||||
    def test_without_confirmation_with_side_effects(
 | 
			
		||||
            self, capsys, commands_with_side_effect, settings):
 | 
			
		||||
        settings.require_confirmation = False
 | 
			
		||||
        assert (ui.select_command(iter(commands_with_side_effect))
 | 
			
		||||
                == commands_with_side_effect[0])
 | 
			
		||||
        assert capsys.readouterr() == ('', 'ls (+side effect)\n')
 | 
			
		||||
        assert capsys.readouterr() == ('', const.USER_COMMAND_MARK + 'ls (+side effect)\n')
 | 
			
		||||
 | 
			
		||||
    def test_with_confirmation(self, capsys, patch_get_key, commands):
 | 
			
		||||
        patch_get_key(['\n'])
 | 
			
		||||
        assert ui.select_command(iter(commands)) == commands[0]
 | 
			
		||||
        assert capsys.readouterr() == ('', u'\x1b[1K\rls [enter/↑/↓/ctrl+c]\n')
 | 
			
		||||
        assert capsys.readouterr() == (
 | 
			
		||||
            '', const.USER_COMMAND_MARK + u'\x1b[1K\rls [enter/↑/↓/ctrl+c]\n')
 | 
			
		||||
 | 
			
		||||
    def test_with_confirmation_abort(self, capsys, patch_get_key, commands):
 | 
			
		||||
        patch_get_key([const.KEY_CTRL_C])
 | 
			
		||||
        assert ui.select_command(iter(commands)) is None
 | 
			
		||||
        assert capsys.readouterr() == ('', u'\x1b[1K\rls [enter/↑/↓/ctrl+c]\nAborted\n')
 | 
			
		||||
        assert capsys.readouterr() == (
 | 
			
		||||
            '', const.USER_COMMAND_MARK + u'\x1b[1K\rls [enter/↑/↓/ctrl+c]\nAborted\n')
 | 
			
		||||
 | 
			
		||||
    def test_with_confirmation_with_side_effct(self, capsys, patch_get_key,
 | 
			
		||||
                                               commands_with_side_effect):
 | 
			
		||||
        patch_get_key(['\n'])
 | 
			
		||||
        assert (ui.select_command(iter(commands_with_side_effect))
 | 
			
		||||
                == commands_with_side_effect[0])
 | 
			
		||||
        assert capsys.readouterr() == ('', u'\x1b[1K\rls (+side effect) [enter/↑/↓/ctrl+c]\n')
 | 
			
		||||
        assert capsys.readouterr() == (
 | 
			
		||||
            '', const.USER_COMMAND_MARK + u'\x1b[1K\rls (+side effect) [enter/↑/↓/ctrl+c]\n')
 | 
			
		||||
 | 
			
		||||
    def test_with_confirmation_select_second(self, capsys, patch_get_key, commands):
 | 
			
		||||
        patch_get_key([const.KEY_DOWN, '\n'])
 | 
			
		||||
        assert ui.select_command(iter(commands)) == commands[1]
 | 
			
		||||
        assert capsys.readouterr() == (
 | 
			
		||||
            '', u'\x1b[1K\rls [enter/↑/↓/ctrl+c]\x1b[1K\rcd [enter/↑/↓/ctrl+c]\n')
 | 
			
		||||
        stderr = (
 | 
			
		||||
            u'{mark}\x1b[1K\rls [enter/↑/↓/ctrl+c]'
 | 
			
		||||
            u'{mark}\x1b[1K\rcd [enter/↑/↓/ctrl+c]\n'
 | 
			
		||||
        ).format(mark=const.USER_COMMAND_MARK)
 | 
			
		||||
        assert capsys.readouterr() == ('', stderr)
 | 
			
		||||
 
 | 
			
		||||
@@ -206,8 +206,7 @@ class TestGetValidHistoryWithoutCurrent(object):
 | 
			
		||||
                            return_value='fuck')
 | 
			
		||||
 | 
			
		||||
    @pytest.fixture(autouse=True)
 | 
			
		||||
    def bins(self, mocker, monkeypatch):
 | 
			
		||||
        monkeypatch.setattr('thefuck.conf.os.environ', {'PATH': 'path'})
 | 
			
		||||
    def bins(self, mocker):
 | 
			
		||||
        callables = list()
 | 
			
		||||
        for name in ['diff', 'ls', 'café']:
 | 
			
		||||
            bin_mock = mocker.Mock(name=name)
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										88
									
								
								thefuck/argument_parser.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										88
									
								
								thefuck/argument_parser.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,88 @@
 | 
			
		||||
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(
 | 
			
		||||
            '--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',
 | 
			
		||||
            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)
 | 
			
		||||
@@ -14,7 +14,7 @@ class Settings(dict):
 | 
			
		||||
    def __setattr__(self, key, value):
 | 
			
		||||
        self[key] = value
 | 
			
		||||
 | 
			
		||||
    def init(self):
 | 
			
		||||
    def init(self, args=None):
 | 
			
		||||
        """Fills `settings` with values from `settings.py` and env."""
 | 
			
		||||
        from .logs import exception
 | 
			
		||||
 | 
			
		||||
@@ -31,6 +31,8 @@ class Settings(dict):
 | 
			
		||||
        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():
 | 
			
		||||
@@ -96,7 +98,7 @@ class Settings(dict):
 | 
			
		||||
        elif attr in ('wait_command', 'history_limit', 'wait_slow_command'):
 | 
			
		||||
            return int(val)
 | 
			
		||||
        elif attr in ('require_confirmation', 'no_colors', 'debug',
 | 
			
		||||
                      'alter_history'):
 | 
			
		||||
                      'alter_history', 'instant_mode'):
 | 
			
		||||
            return val.lower() == 'true'
 | 
			
		||||
        elif attr == 'slow_commands':
 | 
			
		||||
            return val.split(':')
 | 
			
		||||
@@ -109,5 +111,19 @@ class Settings(dict):
 | 
			
		||||
                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)
 | 
			
		||||
 
 | 
			
		||||
@@ -34,6 +34,8 @@ DEFAULT_SETTINGS = {'rules': DEFAULT_RULES,
 | 
			
		||||
                    'wait_slow_command': 15,
 | 
			
		||||
                    'slow_commands': ['lein', 'react-native', 'gradle',
 | 
			
		||||
                                      './gradlew', 'vagrant'],
 | 
			
		||||
                    'repeat': False,
 | 
			
		||||
                    'instant_mode': False,
 | 
			
		||||
                    'env': {'LC_ALL': 'C', 'LANG': 'C', 'GIT_TRACE': '1'}}
 | 
			
		||||
 | 
			
		||||
ENV_TO_ATTR = {'THEFUCK_RULES': 'rules',
 | 
			
		||||
@@ -46,7 +48,9 @@ ENV_TO_ATTR = {'THEFUCK_RULES': 'rules',
 | 
			
		||||
               'THEFUCK_HISTORY_LIMIT': 'history_limit',
 | 
			
		||||
               'THEFUCK_ALTER_HISTORY': 'alter_history',
 | 
			
		||||
               'THEFUCK_WAIT_SLOW_COMMAND': 'wait_slow_command',
 | 
			
		||||
               'THEFUCK_SLOW_COMMANDS': 'slow_commands'}
 | 
			
		||||
               'THEFUCK_SLOW_COMMANDS': 'slow_commands',
 | 
			
		||||
               'THEFUCK_REPEAT': 'repeat',
 | 
			
		||||
               'THEFUCK_INSTANT_MODE': 'instant_mode'}
 | 
			
		||||
 | 
			
		||||
SETTINGS_HEADER = u"""# The Fuck settings file
 | 
			
		||||
#
 | 
			
		||||
@@ -59,3 +63,11 @@ SETTINGS_HEADER = u"""# The Fuck settings file
 | 
			
		||||
#
 | 
			
		||||
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
ARGUMENT_PLACEHOLDER = 'THEFUCK_ARGUMENT_PLACEHOLDER'
 | 
			
		||||
 | 
			
		||||
CONFIGURATION_TIMEOUT = 60
 | 
			
		||||
 | 
			
		||||
USER_COMMAND_MARK = u'\u200B' * 10
 | 
			
		||||
 | 
			
		||||
LOG_SIZE = 1000
 | 
			
		||||
 
 | 
			
		||||
@@ -4,3 +4,7 @@ class EmptyCommand(Exception):
 | 
			
		||||
 | 
			
		||||
class NoRuleMatched(Exception):
 | 
			
		||||
    """Raised when no rule matched for some command."""
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ScriptNotInLog(Exception):
 | 
			
		||||
    """Script not found in log."""
 | 
			
		||||
 
 | 
			
		||||
@@ -6,6 +6,7 @@ import sys
 | 
			
		||||
from traceback import format_exception
 | 
			
		||||
import colorama
 | 
			
		||||
from .conf import settings
 | 
			
		||||
from . import const
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def color(color_):
 | 
			
		||||
@@ -16,6 +17,14 @@ def color(color_):
 | 
			
		||||
        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}'
 | 
			
		||||
@@ -39,7 +48,8 @@ def failed(msg):
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def show_corrected_command(corrected_command):
 | 
			
		||||
    sys.stderr.write(u'{bold}{script}{reset}{side_effect}\n'.format(
 | 
			
		||||
    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),
 | 
			
		||||
@@ -48,9 +58,10 @@ def show_corrected_command(corrected_command):
 | 
			
		||||
 | 
			
		||||
def confirm_text(corrected_command):
 | 
			
		||||
    sys.stderr.write(
 | 
			
		||||
        (u'{clear}{bold}{script}{reset}{side_effect} '
 | 
			
		||||
        (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',
 | 
			
		||||
@@ -121,3 +132,9 @@ def configured_successfully(configuration_details):
 | 
			
		||||
            bold=color(colorama.Style.BRIGHT),
 | 
			
		||||
            reset=color(colorama.Style.RESET_ALL),
 | 
			
		||||
            reload=configuration_details.reload))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def version(thefuck_version, python_version):
 | 
			
		||||
    sys.stderr.write(
 | 
			
		||||
        u'The Fuck {} using Python {}\n'.format(thefuck_version,
 | 
			
		||||
                                                python_version))
 | 
			
		||||
 
 | 
			
		||||
@@ -3,26 +3,30 @@ from .system import init_output
 | 
			
		||||
 | 
			
		||||
init_output()
 | 
			
		||||
 | 
			
		||||
from argparse import ArgumentParser  # noqa: E402
 | 
			
		||||
from pprint import pformat  # noqa: E402
 | 
			
		||||
import sys  # noqa: E402
 | 
			
		||||
import six  # noqa: E402
 | 
			
		||||
from . import logs, types  # noqa: E402
 | 
			
		||||
from .shells import shell  # noqa: E402
 | 
			
		||||
from .conf import settings  # noqa: E402
 | 
			
		||||
from .corrector import get_corrected_commands  # noqa: E402
 | 
			
		||||
from .exceptions import EmptyCommand  # noqa: E402
 | 
			
		||||
from .utils import get_installation_info, get_alias  # noqa: E402
 | 
			
		||||
from .ui import select_command  # noqa: E402
 | 
			
		||||
from .argument_parser import Parser  # noqa: E402
 | 
			
		||||
from .utils import get_installation_info  # noqa: E402
 | 
			
		||||
from .logs import warn  # noqa: E402
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def fix_command():
 | 
			
		||||
def fix_command(known_args):
 | 
			
		||||
    """Fixes previous command. Used when `thefuck` called without arguments."""
 | 
			
		||||
    settings.init()
 | 
			
		||||
    settings.init(known_args)
 | 
			
		||||
    with logs.debug_time('Total'):
 | 
			
		||||
        logs.debug(u'Run with settings: {}'.format(pformat(settings)))
 | 
			
		||||
        raw_command = ([known_args.force_command] if known_args.force_command
 | 
			
		||||
                       else known_args.command)
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            command = types.Command.from_raw_script(sys.argv[1:])
 | 
			
		||||
            command = types.Command.from_raw_script(raw_command)
 | 
			
		||||
        except EmptyCommand:
 | 
			
		||||
            logs.debug('Empty command, nothing to do')
 | 
			
		||||
            return
 | 
			
		||||
@@ -36,34 +40,30 @@ def fix_command():
 | 
			
		||||
            sys.exit(1)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def print_alias():
 | 
			
		||||
    """Prints alias for current shell."""
 | 
			
		||||
    try:
 | 
			
		||||
        alias = sys.argv[2]
 | 
			
		||||
    except IndexError:
 | 
			
		||||
        alias = get_alias()
 | 
			
		||||
 | 
			
		||||
    print(shell.app_alias(alias))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def main():
 | 
			
		||||
    parser = ArgumentParser(prog='thefuck')
 | 
			
		||||
    version = get_installation_info().version
 | 
			
		||||
    parser.add_argument('-v', '--version',
 | 
			
		||||
                        action='version',
 | 
			
		||||
                        version='The Fuck {} using Python {}'.format(
 | 
			
		||||
                            version, sys.version.split()[0]))
 | 
			
		||||
    parser.add_argument('-a', '--alias',
 | 
			
		||||
                        action='store_true',
 | 
			
		||||
                        help='[custom-alias-name] prints alias for current shell')
 | 
			
		||||
    parser.add_argument('command',
 | 
			
		||||
                        nargs='*',
 | 
			
		||||
                        help='command that should be fixed')
 | 
			
		||||
    known_args = parser.parse_args(sys.argv[1:2])
 | 
			
		||||
    parser = Parser()
 | 
			
		||||
    known_args = parser.parse(sys.argv)
 | 
			
		||||
 | 
			
		||||
    if known_args.alias:
 | 
			
		||||
        print_alias()
 | 
			
		||||
    if known_args.help:
 | 
			
		||||
        parser.print_help()
 | 
			
		||||
    elif known_args.version:
 | 
			
		||||
        logs.version(get_installation_info().version,
 | 
			
		||||
                     sys.version.split()[0])
 | 
			
		||||
    elif known_args.command:
 | 
			
		||||
        fix_command()
 | 
			
		||||
        fix_command(known_args)
 | 
			
		||||
    elif known_args.alias:
 | 
			
		||||
        warn("The Fuck will drop Python 2 support soon, more details "
 | 
			
		||||
             "https://github.com/nvbn/thefuck/issues/685")
 | 
			
		||||
 | 
			
		||||
        if known_args.enable_experimental_instant_mode:
 | 
			
		||||
            if six.PY2:
 | 
			
		||||
                warn("Instant mode not supported with Python 2")
 | 
			
		||||
                alias = shell.app_alias(known_args.alias)
 | 
			
		||||
            else:
 | 
			
		||||
                alias = shell.instant_mode_alias(known_args.alias)
 | 
			
		||||
        else:
 | 
			
		||||
            alias = shell.app_alias(known_args.alias)
 | 
			
		||||
 | 
			
		||||
        print(alias)
 | 
			
		||||
    else:
 | 
			
		||||
        parser.print_usage()
 | 
			
		||||
 
 | 
			
		||||
@@ -4,9 +4,11 @@ from .system import init_output
 | 
			
		||||
init_output()
 | 
			
		||||
 | 
			
		||||
import os  # noqa: E402
 | 
			
		||||
from psutil import Process  # noqa: E402
 | 
			
		||||
import json  # noqa: E402
 | 
			
		||||
import time  # noqa: E402
 | 
			
		||||
import six  # noqa: E402
 | 
			
		||||
from . import logs  # 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
 | 
			
		||||
@@ -30,19 +32,41 @@ def _get_not_configured_usage_tracker_path():
 | 
			
		||||
 | 
			
		||||
def _record_first_run():
 | 
			
		||||
    """Records shell pid to tracker file."""
 | 
			
		||||
    with _get_not_configured_usage_tracker_path().open('w') as tracker:
 | 
			
		||||
        tracker.write(six.text_type(_get_shell_pid()))
 | 
			
		||||
    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() or not shell.get_history()[-1] == 'fuck':
 | 
			
		||||
    if not tracker_path.exists():
 | 
			
		||||
        return False
 | 
			
		||||
 | 
			
		||||
    current_pid = _get_shell_pid()
 | 
			
		||||
    with tracker_path.open('r') as tracker:
 | 
			
		||||
        return tracker.read() == six.text_type(current_pid)
 | 
			
		||||
        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):
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										18
									
								
								thefuck/output_readers/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								thefuck/output_readers/__init__.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,18 @@
 | 
			
		||||
from ..conf import settings
 | 
			
		||||
from . import read_log, rerun
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
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, str)
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
    if settings.instant_mode:
 | 
			
		||||
        return read_log.get_output(script)
 | 
			
		||||
    else:
 | 
			
		||||
        return rerun.get_output(script, expanded)
 | 
			
		||||
							
								
								
									
										82
									
								
								thefuck/output_readers/read_log.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										82
									
								
								thefuck/output_readers/read_log.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,82 @@
 | 
			
		||||
import os
 | 
			
		||||
import shlex
 | 
			
		||||
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 ..logs import warn
 | 
			
		||||
from .. import const
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def _group_by_calls(log):
 | 
			
		||||
    script_line = None
 | 
			
		||||
    lines = []
 | 
			
		||||
    for line in log:
 | 
			
		||||
        try:
 | 
			
		||||
            line = line.decode()
 | 
			
		||||
        except UnicodeDecodeError:
 | 
			
		||||
            continue
 | 
			
		||||
 | 
			
		||||
        if const.USER_COMMAND_MARK in line:
 | 
			
		||||
            if script_line:
 | 
			
		||||
                yield script_line, lines
 | 
			
		||||
 | 
			
		||||
            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):
 | 
			
		||||
    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):
 | 
			
		||||
    lines = log_file.readlines()[-const.LOG_SIZE:]
 | 
			
		||||
    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(''.join(script_lines))
 | 
			
		||||
    return screen.display
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def get_output(script):
 | 
			
		||||
    """Reads script output from log.
 | 
			
		||||
 | 
			
		||||
    :type script: str
 | 
			
		||||
    :rtype: (str, str)
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
    if six.PY2:
 | 
			
		||||
        warn('Experimental instant mode is Python 3+ only')
 | 
			
		||||
        return None, None
 | 
			
		||||
 | 
			
		||||
    if 'THEFUCK_OUTPUT_LOG' not in os.environ:
 | 
			
		||||
        warn("Output log isn't specified")
 | 
			
		||||
        return None, None
 | 
			
		||||
 | 
			
		||||
    try:
 | 
			
		||||
        with open(os.environ['THEFUCK_OUTPUT_LOG'], 'rb') as log_file:
 | 
			
		||||
            lines = _get_output_lines(script, log_file)
 | 
			
		||||
            output = '\n'.join(lines).strip()
 | 
			
		||||
            return output, output
 | 
			
		||||
    except OSError:
 | 
			
		||||
        warn("Can't read output log")
 | 
			
		||||
        return None, None
 | 
			
		||||
    except ScriptNotInLog:
 | 
			
		||||
        warn("Script not found in output log")
 | 
			
		||||
        return None, None
 | 
			
		||||
							
								
								
									
										57
									
								
								thefuck/output_readers/rerun.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										57
									
								
								thefuck/output_readers/rerun.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,57 @@
 | 
			
		||||
import os
 | 
			
		||||
import shlex
 | 
			
		||||
from subprocess import Popen, PIPE
 | 
			
		||||
from psutil import Process, TimeoutExpired
 | 
			
		||||
from .. import logs
 | 
			
		||||
from ..conf import settings
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
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):
 | 
			
		||||
            child.kill()
 | 
			
		||||
        proc.kill()
 | 
			
		||||
        return False
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def get_output(script, expanded):
 | 
			
		||||
    """Runs the script and obtains stdin/stderr.
 | 
			
		||||
 | 
			
		||||
    :type script: str
 | 
			
		||||
    :type expanded: str
 | 
			
		||||
    :rtype: (str, str)
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
    env = dict(os.environ)
 | 
			
		||||
    env.update(settings.env)
 | 
			
		||||
 | 
			
		||||
    is_slow = shlex.split(expanded) in settings.slow_commands
 | 
			
		||||
    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=PIPE, env=env)
 | 
			
		||||
        if _wait_output(result, is_slow):
 | 
			
		||||
            stdout = result.stdout.read().decode('utf-8')
 | 
			
		||||
            stderr = result.stderr.read().decode('utf-8')
 | 
			
		||||
 | 
			
		||||
            logs.debug(u'Received stdout: {}'.format(stdout))
 | 
			
		||||
            logs.debug(u'Received stderr: {}'.format(stderr))
 | 
			
		||||
 | 
			
		||||
            return stdout, stderr
 | 
			
		||||
        else:
 | 
			
		||||
            logs.debug(u'Execution timed out!')
 | 
			
		||||
            return None, None
 | 
			
		||||
@@ -6,12 +6,13 @@ from thefuck.specific.git import git_support
 | 
			
		||||
@git_support
 | 
			
		||||
def match(command):
 | 
			
		||||
    return (" is not a git command. See 'git --help'." in command.stderr
 | 
			
		||||
            and 'Did you mean' in command.stderr)
 | 
			
		||||
            and ('The most similar command' in command.stderr
 | 
			
		||||
                 or 'Did you mean' in command.stderr))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@git_support
 | 
			
		||||
def get_new_command(command):
 | 
			
		||||
    broken_cmd = re.findall(r"git: '([^']*)' is not a git command",
 | 
			
		||||
                            command.stderr)[0]
 | 
			
		||||
    matched = get_all_matched_commands(command.stderr)
 | 
			
		||||
    matched = get_all_matched_commands(command.stderr, ['The most similar command', 'Did you mean'])
 | 
			
		||||
    return replace_command(command, broken_cmd, matched)
 | 
			
		||||
 
 | 
			
		||||
@@ -1,3 +1,4 @@
 | 
			
		||||
import re
 | 
			
		||||
from thefuck.utils import replace_argument
 | 
			
		||||
from thefuck.specific.git import git_support
 | 
			
		||||
 | 
			
		||||
@@ -32,5 +33,6 @@ def get_new_command(command):
 | 
			
		||||
        if len(command_parts) > upstream_option_index:
 | 
			
		||||
            command_parts.pop(upstream_option_index)
 | 
			
		||||
 | 
			
		||||
    push_upstream = command.stderr.split('\n')[-3].strip().partition('git ')[2]
 | 
			
		||||
    return replace_argument(" ".join(command_parts), 'push', push_upstream)
 | 
			
		||||
    arguments = re.findall(r'git push (.*)', command.stderr)[0].strip()
 | 
			
		||||
    return replace_argument(" ".join(command_parts), 'push',
 | 
			
		||||
                            'push {}'.format(arguments))
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										14
									
								
								thefuck/rules/git_push_without_commits.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								thefuck/rules/git_push_without_commits.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,14 @@
 | 
			
		||||
import re
 | 
			
		||||
from thefuck.specific.git import git_support
 | 
			
		||||
 | 
			
		||||
fix = u'git commit -m "Initial commit." && {command}'
 | 
			
		||||
refspec_does_not_match = re.compile(r'src refspec \w+ does not match any\.')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@git_support
 | 
			
		||||
def match(command):
 | 
			
		||||
    return bool(refspec_does_not_match.search(command.stderr))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def get_new_command(command):
 | 
			
		||||
    return fix.format(command=command.script)
 | 
			
		||||
@@ -11,7 +11,7 @@ def match(command):
 | 
			
		||||
 | 
			
		||||
@git_support
 | 
			
		||||
def get_new_command(command):
 | 
			
		||||
    return shell.and_('git add .', 'git stash pop', 'git reset .')
 | 
			
		||||
    return shell.and_('git add --update', 'git stash pop', 'git reset .')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# make it come before the other applicable rules
 | 
			
		||||
 
 | 
			
		||||
@@ -1,19 +1,11 @@
 | 
			
		||||
import re
 | 
			
		||||
from thefuck.utils import replace_command, for_app
 | 
			
		||||
from thefuck.utils import for_app
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@for_app('heroku')
 | 
			
		||||
def match(command):
 | 
			
		||||
    return 'is not a heroku command' in command.stderr and \
 | 
			
		||||
           'Perhaps you meant' in command.stderr
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def _get_suggests(stderr):
 | 
			
		||||
    for line in stderr.split('\n'):
 | 
			
		||||
        if 'Perhaps you meant' in line:
 | 
			
		||||
            return re.findall(r'`([^`]+)`', line)
 | 
			
		||||
    return 'Run heroku _ to run' in command.stderr
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def get_new_command(command):
 | 
			
		||||
    wrong = re.findall(r'`(\w+)` is not a heroku command', command.stderr)[0]
 | 
			
		||||
    return replace_command(command, wrong, _get_suggests(command.stderr))
 | 
			
		||||
    return re.findall('Run heroku _ to run ([^.]*)', command.stderr)[0]
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
from thefuck.utils import get_all_executables, memoize, which
 | 
			
		||||
from thefuck.utils import get_all_executables, memoize
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@memoize
 | 
			
		||||
@@ -9,10 +9,13 @@ def _get_executable(script_part):
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def match(command):
 | 
			
		||||
    return (not which(command.script_parts[0])
 | 
			
		||||
    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
 | 
			
		||||
 
 | 
			
		||||
@@ -21,7 +21,8 @@ patterns = ['permission denied',
 | 
			
		||||
            'edspermissionerror',
 | 
			
		||||
            'you don\'t have write permissions',
 | 
			
		||||
            'use `sudo`',
 | 
			
		||||
            'SudoRequiredError']
 | 
			
		||||
            'SudoRequiredError',
 | 
			
		||||
            'error: insufficient privileges']
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def match(command):
 | 
			
		||||
 
 | 
			
		||||
@@ -9,6 +9,6 @@ def match(command):
 | 
			
		||||
 | 
			
		||||
def get_new_command(command):
 | 
			
		||||
    broken = command.script_parts[1]
 | 
			
		||||
    fix = re.findall(r'Did you mean `yarn ([^`]*)`', command.stderr)[0]
 | 
			
		||||
    fix = re.findall(r'Did you mean [`"](?:yarn )?([^`"]*)[`"]', command.stderr)[0]
 | 
			
		||||
 | 
			
		||||
    return replace_argument(command.script, broken, fix)
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
import re
 | 
			
		||||
from subprocess import Popen, PIPE
 | 
			
		||||
from thefuck.utils import for_app, eager, replace_command
 | 
			
		||||
from thefuck.utils import for_app, eager, replace_command, replace_argument
 | 
			
		||||
 | 
			
		||||
regex = re.compile(r'error Command "(.*)" not found.')
 | 
			
		||||
 | 
			
		||||
@@ -10,6 +10,9 @@ def match(command):
 | 
			
		||||
    return regex.findall(command.stderr)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
npm_commands = {'require': 'add'}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@eager
 | 
			
		||||
def _get_all_tasks():
 | 
			
		||||
    proc = Popen(['yarn', '--help'], stdout=PIPE)
 | 
			
		||||
@@ -27,5 +30,9 @@ def _get_all_tasks():
 | 
			
		||||
 | 
			
		||||
def get_new_command(command):
 | 
			
		||||
    misspelled_task = regex.findall(command.stderr)[0]
 | 
			
		||||
    tasks = _get_all_tasks()
 | 
			
		||||
    return replace_command(command, misspelled_task, tasks)
 | 
			
		||||
    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)
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										13
									
								
								thefuck/rules/yarn_command_replaced.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								thefuck/rules/yarn_command_replaced.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,13 @@
 | 
			
		||||
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.stderr)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def get_new_command(command):
 | 
			
		||||
    return regex.findall(command.stderr)[0]
 | 
			
		||||
@@ -1,22 +1,47 @@
 | 
			
		||||
import os
 | 
			
		||||
from uuid import uuid4
 | 
			
		||||
from ..conf import settings
 | 
			
		||||
from ..const import ARGUMENT_PLACEHOLDER, USER_COMMAND_MARK
 | 
			
		||||
from ..utils import memoize
 | 
			
		||||
from .generic import Generic
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Bash(Generic):
 | 
			
		||||
    def app_alias(self, fuck):
 | 
			
		||||
        # It is VERY important to have the variables declared WITHIN the alias
 | 
			
		||||
        alias = "alias {0}='TF_CMD=$(TF_ALIAS={0}" \
 | 
			
		||||
                " PYTHONIOENCODING=utf-8" \
 | 
			
		||||
                " TF_SHELL_ALIASES=$(alias)" \
 | 
			
		||||
                " thefuck $(fc -ln -1)) &&" \
 | 
			
		||||
                " eval $TF_CMD".format(fuck)
 | 
			
		||||
    def app_alias(self, alias_name):
 | 
			
		||||
        # It is VERY important to have the variables declared WITHIN the function
 | 
			
		||||
        return '''
 | 
			
		||||
            function {name} () {{
 | 
			
		||||
                TF_PREVIOUS=$(fc -ln -1);
 | 
			
		||||
                TF_PYTHONIOENCODING=$PYTHONIOENCODING;
 | 
			
		||||
                export TF_ALIAS={name};
 | 
			
		||||
                export TF_SHELL_ALIASES=$(alias);
 | 
			
		||||
                export PYTHONIOENCODING=utf-8;
 | 
			
		||||
                TF_CMD=$(
 | 
			
		||||
                    thefuck $TF_PREVIOUS {argument_placeholder} $@
 | 
			
		||||
                ) && eval $TF_CMD;
 | 
			
		||||
                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 ''))
 | 
			
		||||
 | 
			
		||||
        if settings.alter_history:
 | 
			
		||||
            return alias + "; history -s $TF_CMD'"
 | 
			
		||||
    def instant_mode_alias(self, alias_name):
 | 
			
		||||
        if os.environ.get('THEFUCK_INSTANT_MODE', '').lower() == 'true':
 | 
			
		||||
            return '''
 | 
			
		||||
                export PS1="{user_command_mark}$PS1";
 | 
			
		||||
                {app_alias}
 | 
			
		||||
            '''.format(user_command_mark=USER_COMMAND_MARK,
 | 
			
		||||
                       app_alias=self.app_alias(alias_name))
 | 
			
		||||
        else:
 | 
			
		||||
            return alias + "'"
 | 
			
		||||
            return '''
 | 
			
		||||
                export THEFUCK_INSTANT_MODE=True;
 | 
			
		||||
                export THEFUCK_OUTPUT_LOG={log};
 | 
			
		||||
                script -feq {log};
 | 
			
		||||
                exit
 | 
			
		||||
            '''.format(log='/tmp/thefuck-script-log-{}'.format(uuid4().hex))
 | 
			
		||||
 | 
			
		||||
    def _parse_alias(self, alias):
 | 
			
		||||
        name, value = alias.replace('alias ', '', 1).split('=', 1)
 | 
			
		||||
@@ -41,7 +66,7 @@ class Bash(Generic):
 | 
			
		||||
        if os.path.join(os.path.expanduser('~'), '.bashrc'):
 | 
			
		||||
            config = '~/.bashrc'
 | 
			
		||||
        elif os.path.join(os.path.expanduser('~'), '.bash_profile'):
 | 
			
		||||
            config = '~/.bashrc'
 | 
			
		||||
            config = '~/.bash_profile'
 | 
			
		||||
        else:
 | 
			
		||||
            config = 'bash config'
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -18,7 +18,7 @@ class Fish(Generic):
 | 
			
		||||
            default.add(alias.strip())
 | 
			
		||||
        return default
 | 
			
		||||
 | 
			
		||||
    def app_alias(self, fuck):
 | 
			
		||||
    def app_alias(self, alias_name):
 | 
			
		||||
        if settings.alter_history:
 | 
			
		||||
            alter_history = ('    builtin history delete --exact'
 | 
			
		||||
                             ' --case-sensitive -- $fucked_up_command\n'
 | 
			
		||||
@@ -33,7 +33,7 @@ class Fish(Generic):
 | 
			
		||||
                '  if [ "$unfucked_command" != "" ]\n'
 | 
			
		||||
                '    eval $unfucked_command\n{1}'
 | 
			
		||||
                '  end\n'
 | 
			
		||||
                'end').format(fuck, alter_history)
 | 
			
		||||
                'end').format(alias_name, alter_history)
 | 
			
		||||
 | 
			
		||||
    @memoize
 | 
			
		||||
    @cache('.config/fish/config.fish', '.config/fish/functions')
 | 
			
		||||
@@ -66,6 +66,9 @@ class Fish(Generic):
 | 
			
		||||
    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"eval (thefuck --alias | tr '\n' ';')",
 | 
			
		||||
 
 | 
			
		||||
@@ -3,6 +3,7 @@ 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
 | 
			
		||||
@@ -32,9 +33,13 @@ class Generic(object):
 | 
			
		||||
        """Prepares command for running in shell."""
 | 
			
		||||
        return command_script
 | 
			
		||||
 | 
			
		||||
    def app_alias(self, fuck):
 | 
			
		||||
    def app_alias(self, alias_name):
 | 
			
		||||
        return "alias {0}='eval $(TF_ALIAS={0} PYTHONIOENCODING=utf-8 " \
 | 
			
		||||
               "thefuck $(fc -ln -1))'".format(fuck)
 | 
			
		||||
               "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 ''
 | 
			
		||||
@@ -66,6 +71,9 @@ class Generic(object):
 | 
			
		||||
    def and_(self, *commands):
 | 
			
		||||
        return u' && '.join(commands)
 | 
			
		||||
 | 
			
		||||
    def or_(self, *commands):
 | 
			
		||||
        return u' || '.join(commands)
 | 
			
		||||
 | 
			
		||||
    def how_to_configure(self):
 | 
			
		||||
        return
 | 
			
		||||
 | 
			
		||||
@@ -74,7 +82,7 @@ class Generic(object):
 | 
			
		||||
        encoded = self.encode_utf8(command)
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            splitted = shlex.split(encoded)
 | 
			
		||||
            splitted = [s.replace("??", "\ ") for s in shlex.split(encoded.replace('\ ', '??'))]
 | 
			
		||||
        except ValueError:
 | 
			
		||||
            splitted = encoded.split(' ')
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -2,8 +2,8 @@ from .generic import Generic, ShellConfiguration
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Powershell(Generic):
 | 
			
		||||
    def app_alias(self, fuck):
 | 
			
		||||
        return 'function ' + fuck + ' {\n' \
 | 
			
		||||
    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 $history);\n' \
 | 
			
		||||
 
 | 
			
		||||
@@ -6,10 +6,10 @@ from .generic import Generic
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Tcsh(Generic):
 | 
			
		||||
    def app_alias(self, fuck):
 | 
			
		||||
    def app_alias(self, alias_name):
 | 
			
		||||
        return ("alias {0} 'setenv TF_ALIAS {0} && "
 | 
			
		||||
                "set fucked_cmd=`history -h 2 | head -n 1` && "
 | 
			
		||||
                "eval `thefuck ${{fucked_cmd}}`'").format(fuck)
 | 
			
		||||
                "eval `thefuck ${{fucked_cmd}}`'").format(alias_name)
 | 
			
		||||
 | 
			
		||||
    def _parse_alias(self, alias):
 | 
			
		||||
        name, value = alias.split("\t", 1)
 | 
			
		||||
 
 | 
			
		||||
@@ -1,23 +1,46 @@
 | 
			
		||||
from time import time
 | 
			
		||||
import os
 | 
			
		||||
from uuid import uuid4
 | 
			
		||||
from ..conf import settings
 | 
			
		||||
from ..const import ARGUMENT_PLACEHOLDER, USER_COMMAND_MARK
 | 
			
		||||
from ..utils import memoize
 | 
			
		||||
from .generic import Generic
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Zsh(Generic):
 | 
			
		||||
    def app_alias(self, alias_name):
 | 
			
		||||
        # It is VERY important to have the variables declared WITHIN the alias
 | 
			
		||||
        alias = "alias {0}='TF_CMD=$(TF_ALIAS={0}" \
 | 
			
		||||
                " PYTHONIOENCODING=utf-8" \
 | 
			
		||||
                " TF_SHELL_ALIASES=$(alias)" \
 | 
			
		||||
                " thefuck $(fc -ln -1 | tail -n 1)) &&" \
 | 
			
		||||
                " eval $TF_CMD".format(alias_name)
 | 
			
		||||
        # It is VERY important to have the variables declared WITHIN the function
 | 
			
		||||
        return '''
 | 
			
		||||
            {name} () {{
 | 
			
		||||
                TF_PREVIOUS=$(fc -ln -1 | tail -n 1);
 | 
			
		||||
                TF_CMD=$(
 | 
			
		||||
                    TF_ALIAS={name}
 | 
			
		||||
                    TF_SHELL_ALIASES=$(alias)
 | 
			
		||||
                    PYTHONIOENCODING=utf-8
 | 
			
		||||
                    thefuck $TF_PREVIOUS {argument_placeholder} $*
 | 
			
		||||
                ) && eval $TF_CMD;
 | 
			
		||||
                {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 ''))
 | 
			
		||||
 | 
			
		||||
        if settings.alter_history:
 | 
			
		||||
            return alias + " ; test -n \"$TF_CMD\" && print -s $TF_CMD'"
 | 
			
		||||
    def instant_mode_alias(self, alias_name):
 | 
			
		||||
        if os.environ.get('THEFUCK_INSTANT_MODE', '').lower() == 'true':
 | 
			
		||||
            return '''
 | 
			
		||||
                export PS1="{user_command_mark}$PS1";
 | 
			
		||||
                {app_alias}
 | 
			
		||||
            '''.format(user_command_mark=USER_COMMAND_MARK,
 | 
			
		||||
                       app_alias=self.app_alias(alias_name))
 | 
			
		||||
        else:
 | 
			
		||||
            return alias + "'"
 | 
			
		||||
            return '''
 | 
			
		||||
                export THEFUCK_INSTANT_MODE=True;
 | 
			
		||||
                export THEFUCK_OUTPUT_LOG={log};
 | 
			
		||||
                script -feq {log};
 | 
			
		||||
                exit
 | 
			
		||||
            '''.format(log='/tmp/thefuck-script-log-{}'.format(uuid4().hex))
 | 
			
		||||
 | 
			
		||||
    def _parse_alias(self, alias):
 | 
			
		||||
        name, value = alias.split('=', 1)
 | 
			
		||||
 
 | 
			
		||||
@@ -1,14 +1,13 @@
 | 
			
		||||
from imp import load_source
 | 
			
		||||
from subprocess import Popen, PIPE
 | 
			
		||||
import os
 | 
			
		||||
import sys
 | 
			
		||||
import six
 | 
			
		||||
from psutil import Process, TimeoutExpired
 | 
			
		||||
from . import logs
 | 
			
		||||
from .shells import shell
 | 
			
		||||
from .conf import settings
 | 
			
		||||
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):
 | 
			
		||||
@@ -60,44 +59,6 @@ class Command(object):
 | 
			
		||||
        kwargs.setdefault('stderr', self.stderr)
 | 
			
		||||
        return Command(**kwargs)
 | 
			
		||||
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    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):
 | 
			
		||||
                child.kill()
 | 
			
		||||
            proc.kill()
 | 
			
		||||
            return False
 | 
			
		||||
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def _prepare_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)
 | 
			
		||||
 | 
			
		||||
        script = script.strip()
 | 
			
		||||
        return shell.from_shell(script)
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def from_raw_script(cls, raw_script):
 | 
			
		||||
        """Creates instance of `Command` from a list of script parts.
 | 
			
		||||
@@ -107,29 +68,13 @@ class Command(object):
 | 
			
		||||
        :raises: EmptyCommand
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        script = cls._prepare_script(raw_script)
 | 
			
		||||
        script = format_raw_script(raw_script)
 | 
			
		||||
        if not script:
 | 
			
		||||
            raise EmptyCommand
 | 
			
		||||
 | 
			
		||||
        env = dict(os.environ)
 | 
			
		||||
        env.update(settings.env)
 | 
			
		||||
 | 
			
		||||
        is_slow = script.split(' ')[0] in settings.slow_commands
 | 
			
		||||
        with logs.debug_time(u'Call: {}; with env: {}; is slow: '.format(
 | 
			
		||||
                script, env, is_slow)):
 | 
			
		||||
            result = Popen(script, shell=True, stdin=PIPE,
 | 
			
		||||
                           stdout=PIPE, stderr=PIPE, env=env)
 | 
			
		||||
            if cls._wait_output(result, is_slow):
 | 
			
		||||
                stdout = result.stdout.read().decode('utf-8')
 | 
			
		||||
                stderr = result.stderr.read().decode('utf-8')
 | 
			
		||||
 | 
			
		||||
                logs.debug(u'Received stdout: {}'.format(stdout))
 | 
			
		||||
                logs.debug(u'Received stderr: {}'.format(stderr))
 | 
			
		||||
 | 
			
		||||
                return cls(script, stdout, stderr)
 | 
			
		||||
            else:
 | 
			
		||||
                logs.debug(u'Execution timed out!')
 | 
			
		||||
                return cls(script, None, None)
 | 
			
		||||
        expanded = shell.from_shell(script)
 | 
			
		||||
        stdout, stderr = get_output(script, expanded)
 | 
			
		||||
        return cls(expanded, stdout, stderr)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Rule(object):
 | 
			
		||||
@@ -276,6 +221,22 @@ class CorrectedCommand(object):
 | 
			
		||||
        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.
 | 
			
		||||
 | 
			
		||||
@@ -289,4 +250,5 @@ class CorrectedCommand(object):
 | 
			
		||||
        # This depends on correct setting of PYTHONIOENCODING by the alias:
 | 
			
		||||
        logs.debug(u'PYTHONIOENCODING: {}'.format(
 | 
			
		||||
            os.environ.get('PYTHONIOENCODING', '!!not-set!!')))
 | 
			
		||||
        print(self.script)
 | 
			
		||||
 | 
			
		||||
        print(self._get_script())
 | 
			
		||||
 
 | 
			
		||||
@@ -4,6 +4,7 @@ import sys
 | 
			
		||||
from .conf import settings
 | 
			
		||||
from .exceptions import NoRuleMatched
 | 
			
		||||
from .system import get_key
 | 
			
		||||
from .utils import get_alias
 | 
			
		||||
from . import logs, const
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -69,7 +70,8 @@ def select_command(corrected_commands):
 | 
			
		||||
    try:
 | 
			
		||||
        selector = CommandSelector(corrected_commands)
 | 
			
		||||
    except NoRuleMatched:
 | 
			
		||||
        logs.failed('No fucks given')
 | 
			
		||||
        logs.failed('No fucks given' if get_alias() == 'fuck'
 | 
			
		||||
                    else 'Nothing found')
 | 
			
		||||
        return
 | 
			
		||||
 | 
			
		||||
    if not settings.require_confirmation:
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,5 @@
 | 
			
		||||
import os
 | 
			
		||||
import pickle
 | 
			
		||||
import pkg_resources
 | 
			
		||||
import re
 | 
			
		||||
import shelve
 | 
			
		||||
import six
 | 
			
		||||
@@ -8,7 +7,7 @@ from contextlib import closing
 | 
			
		||||
from decorator import decorator
 | 
			
		||||
from difflib import get_close_matches
 | 
			
		||||
from functools import wraps
 | 
			
		||||
from warnings import warn
 | 
			
		||||
from .logs import warn
 | 
			
		||||
from .conf import settings
 | 
			
		||||
from .system import Path
 | 
			
		||||
 | 
			
		||||
@@ -108,15 +107,16 @@ def get_all_executables():
 | 
			
		||||
            return fallback
 | 
			
		||||
 | 
			
		||||
    tf_alias = get_alias()
 | 
			
		||||
    tf_entry_points = get_installation_info().get_entry_map()\
 | 
			
		||||
                                             .get('console_scripts', {})\
 | 
			
		||||
                                             .keys()
 | 
			
		||||
    tf_entry_points = ['thefuck', 'fuck']
 | 
			
		||||
 | 
			
		||||
    bins = [exe.name.decode('utf8') if six.PY2 else exe.name
 | 
			
		||||
            for path in os.environ.get('PATH', '').split(':')
 | 
			
		||||
            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 for alias in shell.get_aliases() if alias != tf_alias]
 | 
			
		||||
    aliases = [alias.decode('utf8') if six.PY2 else alias
 | 
			
		||||
               for alias in shell.get_aliases() if alias != tf_alias]
 | 
			
		||||
 | 
			
		||||
    return bins + aliases
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -138,12 +138,17 @@ def eager(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'):
 | 
			
		||||
        if separator in line:
 | 
			
		||||
            should_yield = True
 | 
			
		||||
        elif should_yield and line:
 | 
			
		||||
            yield line.strip()
 | 
			
		||||
        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):
 | 
			
		||||
@@ -247,6 +252,8 @@ cache.disabled = False
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def get_installation_info():
 | 
			
		||||
    import pkg_resources
 | 
			
		||||
 | 
			
		||||
    return pkg_resources.require('thefuck')[0]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -275,3 +282,18 @@ def get_valid_history_without_current(command):
 | 
			
		||||
    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.strip()
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user