1
0
mirror of https://github.com/nvbn/thefuck.git synced 2025-11-01 15:42:06 +00:00

Compare commits

...

282 Commits
3.11 ... 3.23

Author SHA1 Message Date
Vladimir Iakovlev
db12211e05 Bump to 3.23 2017-08-29 09:39:32 +02:00
Vladimir Iakovlev
7a0db1899c #685: Warn about Python 2 only on Python 2 2017-08-29 09:39:24 +02:00
Vladimir Iakovlev
e5255c3278 Bump to 3.22 2017-08-29 05:02:16 +02:00
Vladimir Iakovlev
d44b11fbd8 #682: Fix gif link 2017-08-28 03:39:17 +02:00
Vladimir Iakovlev
3472026d5e #685: Warn about Python 2 support 2017-08-28 03:38:14 +02:00
Vladimir Iakovlev
bf3c16816d Merge pull request #684 from nvbn/682-instant-fuck-mode
682 instant fuck mode
2017-08-28 04:35:51 +03:00
Vladimir Iakovlev
6fac0622e5 #682: Warn on instant mode with Python 2 2017-08-28 03:21:15 +02:00
Vladimir Iakovlev
1b694fae7b #682: Fix gif link 2017-08-26 14:41:05 +02:00
Vladimir Iakovlev
2ebfb92760 #682: Add gif with instant mode 2017-08-26 14:39:36 +02:00
Vladimir Iakovlev
9cb04ac631 #682: Make warnings more visible 2017-08-26 14:30:19 +02:00
Vladimir Iakovlev
5504b905f3 #682: Fix git_push rule in instant mode 2017-08-26 13:39:38 +02:00
Vladimir Iakovlev
e707728fd5 #682: Update readme 2017-08-26 13:31:09 +02:00
Vladimir Iakovlev
3d98aad5df Merge branch 'master' into 682-instant-fuck-mode 2017-08-26 13:25:59 +02:00
Vladimir Iakovlev
b72ad2907f #682: Allow THEFUCK_INSTANT_MODE=False 2017-08-26 13:21:24 +02:00
Vladimir Iakovlev
7a57355e7e #682: Disable instant mode on Python 2 2017-08-26 13:16:10 +02:00
Vladimir Iakovlev
1132015e60 #682: Rename output to output_readers 2017-08-26 12:45:49 +02:00
Vladimir Iakovlev
0ecc86eda6 #682: Fix aliases in instant mode 2017-08-26 06:29:38 +02:00
Vladimir Iakovlev
c4848d1816 #682: Fix tests in python 2 2017-08-26 06:20:52 +02:00
Vladimir Iakovlev
31becc9456 #682: Fix tests and flake8 2017-08-26 06:16:51 +02:00
Vladimir Iakovlev
cd3a3cd823 #682: Implement instant mode aliases for bash and zsh 2017-08-26 05:46:07 +02:00
Vladimir Iakovlev
f9b30ae2d3 #683: Mention -y and -r in the readme 2017-08-26 04:57:16 +02:00
Vladimir Iakovlev
832ef96188 #681: Lower priority of missing_space_before_subcommand rule 2017-08-25 11:47:17 +02:00
Vladimir Iakovlev
20e678a38a #682: Implement experimental instant mode 2017-08-25 11:44:07 +02:00
Vladimir Iakovlev
f76d2061d1 Merge pull request #680 from simonwhitaker/patch-1
Fix docs for Command type
2017-08-23 09:37:13 +03:00
Simon Whitaker
16ec6a7d2a Fix docs for Command type 2017-08-23 07:14:56 +01:00
Vladimir Iakovlev
6c4333944f Bump to 3.21 2017-08-21 12:26:19 +02:00
Vladimir Iakovlev
31f5185642 Merge pull request #679 from nvbn/678-speedup-thefuck-alias
678 speedup thefuck
2017-08-21 13:25:33 +03:00
Vladimir Iakovlev
d71dbc5de4 #678: Speedup fuck by hardcoding entry points 2017-08-21 11:55:34 +02:00
Vladimir Iakovlev
fabef80056 #678: Import pkg_resources only when it needed 2017-08-21 11:50:04 +02:00
Vladimir Iakovlev
b4c4fdf706 #678: Use fastentrypoints 2017-08-21 11:32:23 +02:00
Vladimir Iakovlev
d267488520 Bump to 3.20 2017-08-16 11:28:59 +02:00
Vladimir Iakovlev
e31124335f #658: Ensure that history isn't empty in autoconfiguration 2017-08-16 11:26:43 +02:00
Vladimir Iakovlev
71a5182b9a Merge pull request #676 from nvbn/662-fix-autoconfig
#662: Autoconfigure when `fuck` was called < 60 seconds ago from the same shell
2017-08-08 17:36:10 +02:00
Vladimir Iakovlev
6a096155dc #662: Autoconfigure when fuck was called < 60 seconds ago from the same shell 2017-08-08 16:13:37 +02:00
Vladimir Iakovlev
5742d2d910 #N/A: Use real PATH in tests 2017-08-03 12:30:04 +02:00
Vladimir Iakovlev
754bb3e21f #N/A: Reset environment variables in tests 2017-08-03 12:18:05 +02:00
Vladimir Iakovlev
2bbba9a0c8 Bump to 3.19 2017-08-03 11:34:01 +02:00
Vladimir Iakovlev
b978c3793e Merge pull request #669 from tomoshi0809/master
#630 Catching the escaped space in filenames
2017-07-22 20:11:03 +02:00
KEI
8a83b30e73 Corrected the part for splitting a command 2017-07-19 00:09:21 +09:00
Vladimir Iakovlev
fd20a3f832 Merge pull request #657 from josephfrazier/git_not_command-wording
Update stderr wording of git_not_command
2017-06-19 23:12:46 +02:00
Joseph Frazier
b6ed499103 Make git_not_command stderr detection backward-compatible 2017-06-06 13:56:13 -04:00
Joseph Frazier
76600cf40a Update stderr wording of git_not_command
This changed in git v2.13.1, see
6c48686263 (diff-081cf476dd9ac3b05c183570de47cb23)
2017-06-05 17:29:42 -04:00
Vladimir Iakovlev
e62666181a #650: #651: #646: Recommend to install thefuck globally 2017-05-29 10:11:15 +02:00
Vladimir Iakovlev
c88b0792b8 Bump to 3.18 2017-05-10 16:51:19 +02:00
Vladimir Iakovlev
06a89427e2 #N/A: Fix bash alias on ci 2017-05-10 16:40:57 +02:00
Vladimir Iakovlev
3a134f250d Bump to 3.17 2017-05-10 15:34:06 +02:00
Vladimir Iakovlev
b54cdf7c49 #637: Suggest yarn add on yarn require 2017-05-10 15:32:11 +02:00
Vladimir Iakovlev
1b05a497e8 #635: Show "Nothing found" instead of 'No fucks given' when different alias are used 2017-05-10 15:22:26 +02:00
Vladimir Iakovlev
79602383ec #549: Fix aliases with bash 2017-05-10 15:14:01 +02:00
Vladimir Iakovlev
84c42168df #N/A: Add new line after version 2017-05-10 15:06:29 +02:00
Vladimir Iakovlev
f53d772ac3 Merge pull request #640 from bam241/add_macport_to_sudo
fix sudo.py for macport
2017-05-03 12:08:53 +04:00
Mouginot B
93d4a4fc3a fix sudo.py for macport 2017-05-02 17:36:35 -05:00
Vladimir Iakovlev
2cb23b1805 #N/A: Fix docstring 2017-05-01 17:49:13 +02:00
Vladimir Iakovlev
33f28cf76d #633: Show ci badges for master 2017-04-20 21:34:47 +02:00
Vladimir Iakovlev
6322dbd9ed #N/A: Fix flake8 warnings 2017-04-10 23:23:23 +02:00
Vladimir Iakovlev
fc09818351 Bump to 3.16 2017-04-10 23:16:06 +02:00
Vladimir Iakovlev
2788ef1471 #N/A: Make missing_space_before_subcommand handle aliases correctly 2017-04-10 23:15:12 +02:00
Vladimir Iakovlev
ef3aabe7c5 Merge branch '614-repeate-option' 2017-03-28 18:51:25 +02:00
Vladimir Iakovlev
2af54d036d #623: Fix UnicodeDecodeError with Python 2 2017-03-28 18:50:51 +02:00
Vladimir Iakovlev
99c10b50ff Merge branch 'dfadev-master' 2017-03-28 18:35:54 +02:00
Vladimir Iakovlev
802fcd96fd #621: Refine yarn_command_replaced rule tests 2017-03-28 18:35:40 +02:00
Russ Panula
900e83e028 add rule for: yarn install [pkg]
--- `install` has been replaced with `add` to add new dependencies. Run $0 instead.

6e9a9a6596/src/reporters/lang/en.js (L18)
2017-03-28 18:31:01 +02:00
Joseph Frazier
d41cbb6810 Fix heroku_not_command for new stderr format
heroku updated its command suggestion formatting, so account for that.
For example:

    $ heroku log
     ▸    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.
    $ fuck
    heroku logs [enter/↑/↓/ctrl+c]
2017-03-28 18:31:01 +02:00
Vladimir Iakovlev
b36cf59b46 #614: Refine argument_parser 2017-03-28 18:18:01 +02:00
Vladimir Iakovlev
cfa831c88d #614: Add --repeat option 2017-03-28 18:09:38 +02:00
Vladimir Iakovlev
818d06fb95 Merge pull request #622 from josephfrazier/heroku-format
Fix heroku_not_command for new stderr format
2017-03-28 16:56:03 +04:00
Vladimir Iakovlev
c3eca8234a #620: Add --debug 2017-03-28 13:09:11 +02:00
Vladimir Iakovlev
d47ff8cbf2 #620: Fix functional tests 2017-03-28 12:28:34 +02:00
Vladimir Iakovlev
1a52e98fbd #620: Fix code style 2017-03-28 12:25:33 +02:00
Vladimir Iakovlev
53c11d2ef4 #620: Fix python 2 support 2017-03-28 12:08:32 +02:00
Vladimir Iakovlev
beda1854cf #620: Add bash support 2017-03-28 12:01:09 +02:00
Vladimir Iakovlev
7532c65c62 #620: Fix aliases with zsh 2017-03-28 11:38:28 +02:00
Vladimir Iakovlev
ec37998a10 #620: Add support of arguments to fuck, like fuck -y on zsh 2017-03-28 11:31:06 +02:00
Joseph Frazier
58d5eff6d0 Fix heroku_not_command for new stderr format
heroku updated its command suggestion formatting, so account for that.
For example:

    $ heroku log
     ▸    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.
    $ fuck
    heroku logs [enter/↑/↓/ctrl+c]
2017-03-26 15:55:03 -04:00
Vladimir Iakovlev
d28567bb31 #585: Fix suggestion of .bash_profile 2017-03-23 16:55:24 +01:00
Vladimir Iakovlev
b016bb2255 Merge pull request #619 from josephfrazier/yarn-alias-scripts
Extend yarn_alias rule to handle package.json scripts
2017-03-23 19:39:11 +04:00
Joseph Frazier
bf109ee548 Extend yarn_alias rule to handle package.json scripts
For example, if an "etl" script is defined in package.json, it can be
run with `yarn etl`. However, if `yarn etil` is run, `yarn` will
suggest the correction. This change lets `thefuck` take advantage of
that:

    $ yarn etil
    yarn etil v0.21.3
    error Command "etil" not found. Did you mean "etl"?
    $ fuck
    yarn etl [enter/?/?/ctrl+c]
2017-03-22 16:52:30 -04:00
Vladimir Iakovlev
1aaaca1220 Merge branch 'Asday-master' 2017-03-22 14:00:18 +01:00
Vladimir Iakovlev
b096560469 #618: Refine git_push_without_commits rule 2017-03-22 14:00:03 +01:00
Vladimir Iakovlev
5b1f3ff816 Merge branch 'master' of git://github.com/Asday/thefuck into Asday-master 2017-03-22 13:57:18 +01:00
Vladimir Iakovlev
c5f7c89222 Merge pull request #617 from josephfrazier/git-stash-add-updated
git_stash_pop: Add only updated files
2017-03-22 15:25:25 +04:00
Adam Barnes
e61271dae3 Removed another unused import.
Goodness.
2017-03-22 10:59:27 +00:00
Adam Barnes
bddb43b987 Removed an unused import. 2017-03-22 10:29:50 +00:00
Adam Barnes
b22a3ac891 Created a rule for trying to push a new repository with no commits. 2017-03-22 10:23:35 +00:00
Joseph Frazier
f4cc88f6c7 git_stash_pop: Add only updated files
This avoids adding untracked files to the repo. See here for a
description of the difference between `git add .` and `git add --update`:

https://stackoverflow.com/questions/572549/difference-between-git-add-a-and-git-add/572660#572660
2017-03-21 20:12:15 -04:00
Vladimir Iakovlev
5734412d82 Bump to 3.15 2017-03-14 16:38:07 +01:00
Vladimir Iakovlev
24588b23e2 #N/A: Add .tox to flake8 exclude 2017-03-14 11:14:23 +01:00
Vladimir Iakovlev
825f7986c7 #322: Use unicode in vagrant_up rule 2017-03-14 11:13:43 +01:00
Vladimir Iakovlev
971c7e1b3f #322: Add vagrant to slow commands 2017-03-14 11:13:10 +01:00
Vladimir Iakovlev
8375b78877 #N/A: Use unicode in all log functions 2017-03-14 09:49:36 +01:00
Vladimir Iakovlev
6f39edc155 #611: Force use MagicMock in tests 2017-03-13 23:35:57 +01:00
Vladimir Iakovlev
408cb5fa09 #611: Fix python 2 support 2017-03-13 23:26:57 +01:00
Vladimir Iakovlev
2315929875 #579: Add missing_space_before_subcommand rule 2017-03-13 22:21:34 +01:00
Vladimir Iakovlev
14a9cd85aa #611: Allow to configure alias automatically by calling fuck twice 2017-03-13 21:50:13 +01:00
Vladimir Iakovlev
2379573cf2 #591: Add path_from_history rule 2017-03-13 19:05:34 +01:00
Vladimir Iakovlev
350be285b8 #591: Treat builtin commands as executables 2017-03-13 18:27:01 +01:00
Vladimir Iakovlev
76aa5546df #593: Remove unned underscores from readme 2017-03-13 17:22:58 +01:00
Vladimir Iakovlev
5179015b84 #531: Add example of alias for running without confirmation to readme 2017-03-13 16:59:29 +01:00
Vladimir Iakovlev
dee99ed705 #220: Use pip3 install --user for upgrade too 2017-03-13 14:15:24 +01:00
Vladimir Iakovlev
e101f1fcc9 #220: Use pip3 install --user in readme 2017-03-13 14:01:38 +01:00
Vladimir Iakovlev
73b884df5f Merge branch 'ds-forks-master' 2017-03-13 13:53:55 +01:00
Vladimir Iakovlev
9e8b4f594d #602: Little cleanup 2017-03-13 13:53:43 +01:00
Vladimir Iakovlev
c2b597f22b Merge branch 'master' of https://github.com/ds-forks/thefuck into ds-forks-master 2017-03-13 13:47:56 +01:00
Vladimir Iakovlev
8c783b7405 Merge branch 'awonnacott-master' 2017-03-13 13:47:10 +01:00
Vladimir Iakovlev
3efa42ec06 Merge branch 'master' of https://github.com/awonnacott/thefuck into awonnacott-master 2017-03-13 13:45:45 +01:00
Vladimir Iakovlev
02bcd8331d Merge branch 'josephfrazier-flake8' 2017-03-13 13:39:16 +01:00
Vladimir Iakovlev
bd750ff9a3 #563: Exclude build from flake8 check 2017-03-13 13:39:00 +01:00
Vladimir Iakovlev
725ef271b1 Merge branch 'flake8' of https://github.com/josephfrazier/thefuck into josephfrazier-flake8
# Conflicts:
#	thefuck/system/unix.py
#	thefuck/system/win32.py
2017-03-13 13:35:11 +01:00
Vladimir Iakovlev
d2f8cebfd8 Merge branch 'josephfrazier-yarn_help' 2017-03-13 13:32:02 +01:00
Vladimir Iakovlev
c7d7a6d1d7 #612: Little cleanup 2017-03-13 13:30:07 +01:00
Joseph Frazier
4b53b1d3e3 Support Linux/Windows in yarn_help rule
See https://www.dwheeler.com/essays/open-files-urls.html
and https://stackoverflow.com/questions/5226958/which-equivalent-function-in-python/15133367#15133367
2017-03-10 15:22:48 -05:00
Joseph Frazier
35ea4dce71 Add yarn_help rule
Yarn likes to keep its documentation online, rather than in `yarn help`
output. For example, `yarn help clean` doesn't tell you anything about
the `clean` subcommand. Instead, it points you towards
https://yarnpkg.com/en/docs/cli/clean

This rule detects when that happens, and suggests opening the URL. One
caveat is the currently only OSX is supported, as Linux uses `xdg-open`
instead of `open`.
2017-03-10 13:07:46 -05:00
Joseph Frazier
2fea1f3846 Run flake8 on AppVeyor 2017-03-10 12:18:59 -05:00
Joseph Frazier
e009f0a05b Fix flake8 errors: E305 expected 2 blank lines after class or function definition 2017-03-08 19:53:54 -05:00
Joseph Frazier
78515c7bbb Fix flake8 errors: W391 blank line at end of file 2017-03-08 12:43:34 -05:00
Joseph Frazier
62a845fd94 fixup! Fix flake8 errors: E302 expected 2 blank lines, found 1 2017-03-08 12:43:05 -05:00
Joseph Frazier
2c7ce91dd5 Fix flake8 errors: F401 'sys' imported but unused 2017-03-08 12:42:24 -05:00
Joseph Frazier
c775937d17 fixup! Fix flake8 errors: E101/W191 indentation contains (mixed spaces and) tabs 2017-03-08 12:40:11 -05:00
Joseph Frazier
aaf01394db fixup! Fix flake8 errors: E126 continuation line over-indented for hanging indent 2017-03-08 12:39:24 -05:00
Joseph Frazier
0b0a2220a0 fixup! Ignore flake8 errors with inline comments: W291 trailing whitespace 2017-03-08 12:34:49 -05:00
Joseph Frazier
b038ea4541 Merge branch 'master' into flake8 2017-03-08 12:21:54 -05:00
Vladimir Iakovlev
7d3ddfc8d9 Merge branch 'josephfrazier-yarn-command-not-found' 2017-03-06 17:32:34 +01:00
Vladimir Iakovlev
02f3250d39 #609: Use replace_command in yarn_command_not_found 2017-03-06 17:31:57 +01:00
Joseph Frazier
df5428c5e4 Add yarn_command_not_found rule
This addresses https://github.com/nvbn/thefuck/pull/607#issuecomment-283945505

The code was adapted from the `grunt_task_not_found` rule
2017-03-03 23:38:20 -05:00
Vladimir Iakovlev
ef5ff6210a Merge pull request #606 from josephfrazier/git-rm-staged
Add git_rm_staged rule for removing locally staged changes
2017-03-03 13:52:43 +01:00
Vladimir Iakovlev
fbb73f262b Merge pull request #607 from josephfrazier/yarn-alias
Fix aliased `yarn` commands like `yarn ls`
2017-03-03 13:50:58 +01:00
Vladimir Iakovlev
20246e5be6 Merge pull request #608 from MattKotsenas/bugfix/no-history
Update PowerShell alias to handle no history
2017-03-03 13:45:23 +01:00
Matt Kotsenas
4669a033ee Update PowerShell alias to handle no history
If history is cleared (or the shell is new and there is no history),
invoking thefuck results in an error because the alias attempts to execute
the usage string.

The fix is to check if Get-History returns anything before invoking
thefuck.
2017-03-01 09:36:30 -08:00
Joseph Frazier
7e16a2eb7c Fix aliased yarn commands like yarn ls
[Yarn] has a handful of subcommand [aliases], but does not automatically
[correct] them for the user. This makes it so that `fuck` will do the
trick. For example:

    $ yarn ls
    yarn ls v0.20.3
    error Did you mean `yarn list`?
    info Visit https://yarnpkg.com/en/docs/cli/list for documentation about this command.
    $ fuck
    yarn list [enter/?/?/ctrl+c]

[Yarn]: https://yarnpkg.com/en/
[aliases]: 0adbc59b18/src/cli/aliases.js
[correct]: https://github.com/yarnpkg/yarn/pull/1044#issuecomment-253763230
2017-02-26 20:50:41 -05:00
Joseph Frazier
42ec01dab1 Add git_rm_staged rule for removing locally staged changes
It would be nice if `thefuck` could help me `git rm` a file I had
already staged. This rule, adapted from `git_rm_local_modifications`,
does that.
2017-02-26 19:16:15 -05:00
Vladimir Iakovlev
91c27e1a62 Merge pull request #605 from andrew-epstein/master
Improve performance of history rule
2017-02-24 13:46:56 +01:00
Andrew Epstein
778f2bdf1a Improve performance of history rule 2017-02-22 07:56:40 -05:00
Vladimir Iakovlev
e893521872 #N/A: Run coveralls only on full test run (python 3.6 with linux) 2017-02-09 16:13:09 +01:00
Vladimir Iakovlev
bbed17fe07 #N/A: Add sudo_command_from_user_path rule 2017-02-09 16:09:37 +01:00
dhilipsiva
309fe8f6ee Fix test cases 2017-02-09 09:16:20 +05:30
dhilipsiva
8a8ade1e6b Fix regex 2017-02-09 08:52:20 +05:30
dhilipsiva
7f9025c7ad Make Changes suggested by @nvbn
* remove comments/doctrings at the top of files;
* move sudo-related stuff to sudo rule;
* for no_command case try to find most similar command, like, for example, in react_native_command_unrecognized rule.
2017-02-09 08:46:54 +05:30
Vladimir Iakovlev
6f842ab747 Merge pull request #604 from wbolster/add-colemak-nav-bindings
add support for colemak style vi bindings
2017-02-08 13:01:28 +01:00
wouter bolsterlee
ae68bcbac1 add support for colemak style vi bindings
this allows e/n in addition to j/k (same places on the keyboard on
colemak and qwerty) to be used as arrow keys when selecting a command
from the suggested fixups.

fixes #603
2017-02-08 12:06:11 +01:00
dhilipsiva
fb07cdfb4a Add hostscli to readme 2017-02-05 14:58:36 +05:30
dhilipsiva
55dcf06569 Hostscli 2017-02-05 14:50:36 +05:30
Vladimir Iakovlev
28a0150e45 Merge branch 'juzim-apt_get_not_installed' 2017-01-30 13:06:34 +01:00
Vladimir Iakovlev
430a7135af #599: Remove unused import 2017-01-30 13:06:19 +01:00
Vladimir Iakovlev
ff2be6c9a3 Merge branch 'apt_get_not_installed' of git://github.com/juzim/thefuck into juzim-apt_get_not_installed 2017-01-30 13:03:28 +01:00
Vladimir Iakovlev
4748776296 Merge pull request #598 from josephfrazier/git_add_force
Add git_add_force rule
2017-01-30 13:02:20 +01:00
Vladimir Iakovlev
1885196a11 Merge pull request #597 from josephfrazier/readme-git_stash-typo
README.md: fix typo in git_stash description
2017-01-30 13:00:42 +01:00
Vladimir Iakovlev
c127040a4c Merge pull request #596 from josephfrazier/git-tag-force
Add git_tag_force rule
2017-01-30 13:00:19 +01:00
Julian Zimmermann
ac7b633e28 Added support for "not installed" message in apt_get 2017-01-29 00:15:55 +01:00
Joseph Frazier
4d0388b53c Add git_add_force rule
This adds `--force` to `git add` when needed. For example:

    $ git add dist/*.js
    The following paths are ignored by one of your .gitignore files:
    dist/app.js
    dist/background.js
    dist/options.js
    Use -f if you really want to add them.
    $ fuck
    git add --force dist/app.js dist/background.js dist/options.js [enter/↑/↓/ctrl+c]
    $
2017-01-28 13:26:40 -05:00
Joseph Frazier
8da4dce5f2 Add git_tag_force rule
This adds `--force` to `git tag` when needed. For example:

    $ git tag alert
    fatal: tag 'alert' already exists
    $ fuck
    git tag --force alert [enter/↑/↓/ctrl+c]
    Updated tag 'alert' (was dec6956)
    $
2017-01-28 13:26:14 -05:00
Joseph Frazier
ace6e88269 README.md: fix typo in git_stash description
from "stashes you local modifications"
to   "stashes your local modifications"
2017-01-27 19:44:40 -05:00
Vladimir Iakovlev
a015c0f5e2 #N/A: Add gem unknown command rule 2017-01-15 15:14:53 +01:00
Vladimir Iakovlev
dbe324bcd8 #587: Add scm correction rule 2017-01-15 14:40:50 +01:00
Vladimir Iakovlev
8447b5caa2 #585: Add note about reloading changes in how to configure message 2017-01-15 14:03:09 +01:00
Vladimir Iakovlev
3a9942061d Bump to 3.14 2017-01-11 15:05:29 +01:00
Vladimir Iakovlev
a65f90813b Bump to 3.13 2017-01-11 14:59:18 +01:00
Vladimir Iakovlev
a778ea6203 #588: Stop using bashlex 2017-01-11 14:58:50 +01:00
Vladimir Iakovlev
03a828d586 Bump to 3.12 2017-01-09 18:17:50 +01:00
Vladimir Iakovlev
4a0d71c1c4 #N/A: Add ifconfig_device_not_found rule 2017-01-09 18:13:37 +01:00
Vladimir Iakovlev
a6f63c0568 #580: Use bashlex in generic shell 2017-01-09 17:50:23 +01:00
Vladimir Iakovlev
d1b9492085 Merge pull request #586 from duboviy/master
Add Python 3.6 support
2017-01-09 17:38:37 +01:00
Eugene Duboviy
993a661c60 Update .travis.yml 2017-01-08 17:13:22 +02:00
Eugene Duboviy
bc9121cb13 Update appveyor.yml 2017-01-08 17:08:38 +02:00
Eugene Duboviy
7db140c456 Update tox.ini 2017-01-08 17:06:45 +02:00
Vladimir Iakovlev
e313ff73a9 Merge pull request #582 from josephfrazier/git_stash_pop
Fix `git stash pop` with local changes
2016-12-22 14:02:05 +01:00
Joseph Frazier
8c62706db4 Fix git stash pop with local changes
When there are local changes to a file, and a git stash is popped that
contains other changes to that same file, git fails as follows:

    $ git stash pop
    error: Your local changes to the following files would be overwritten by merge:
            src/index.js
    Please commit your changes or stash them before you merge.
    Aborting
    $

This change adds a rule that corrects this problem as suggested [here]:

    $ git stash pop
    error: Your local changes to the following files would be overwritten by merge:
            src/index.js
    Please commit your changes or stash them before you merge.
    Aborting
    $ fuck
    git add . && git stash pop && git reset . [enter/↑/↓/ctrl+c]
    Auto-merging src/index.js
    On branch flow
    Changes to be committed:
      (use "git reset HEAD <file>..." to unstage)

            modified:   src/index.js

    Changes not staged for commit:
      (use "git add <file>..." to update what will be committed)
      (use "git checkout -- <file>..." to discard changes in working directory)

            modified:   src/index.js

    Dropped refs/stash@{0} (f94776d484c4278997ac6837a7b138b9b9cdead1)
    Unstaged changes after reset:
    M        src/index.js
    $

[here]: https://stackoverflow.com/questions/15126463/how-do-i-merge-local-modifications-with-a-git-stash-without-an-extra-commit/15126489#15126489
2016-12-11 12:44:04 -05:00
Vladimir Iakovlev
6baa7f650e Merge pull request #580 from josephfrazier/bash-command-substitution
bash: fix parsing of command substitution
2016-11-30 15:49:47 +01:00
Joseph Frazier
4ae32cf4ee bash: use generic shell's UTF-8 methods 2016-11-23 22:42:16 -05:00
Joseph Frazier
385746850e generic shell: extract UTF-8 encoding/decoding into methods 2016-11-23 07:53:22 -05:00
Joseph Frazier
4f87141f0c bash: fallback to generic parser if bashlex fails 2016-11-23 07:43:25 -05:00
Joseph Frazier
dbedcc7aa6 Test parsing bash arithmetic expressions 2016-11-23 07:36:58 -05:00
Vladimir Iakovlev
e0b5d47fa5 Merge pull request #578 from scorphus/fish-builtin-history
#577: Use builtin `history` in Fish function
2016-11-22 12:03:06 +01:00
Joseph Frazier
ca44ee0640 bash: use bashlex for split_command, not shlex 2016-11-18 14:43:07 -05:00
Joseph Frazier
892e8a8e65 Test parsing bash command substitution
This is to help address bad corrections like the following (note the
position of the -p flag):

    thefuck 'git log $(git ls-files thefuck | grep python_command) -p'
    git log $(git ls-files thefuck | grep -p python_command) [enter/↑/↓/ctrl+c]
2016-11-18 14:43:00 -05:00
Pablo Santiago Blum de Aguiar
a947259eef #577: Use builtin history in Fish function
Fix #577
2016-11-17 22:57:11 -02:00
Vladimir Iakovlev
785cb83ff3 Merge branch 'josephfrazier-git-flag-after-filename' 2016-11-08 23:53:50 +01:00
Vladimir Iakovlev
aec8fe3233 #570: Refine tests 2016-11-08 23:53:40 +01:00
Vladimir Iakovlev
c21dbd2be3 Merge branch 'git-flag-after-filename' of https://github.com/josephfrazier/thefuck into josephfrazier-git-flag-after-filename 2016-11-08 23:48:40 +01:00
Andrew Wonnacott
b4fda04acb Now only return [] when correct error was caught 2016-11-03 03:46:27 -04:00
Andrew Wonnacott
5f6c55d839 Fix issue with attempting to scroll through options when not-found package has no packages with matching names causing crash. 2016-11-03 03:34:41 -04:00
Vladimir Iakovlev
6173913291 Merge pull request #572 from josephfrazier/ls_all
Suggest `ls -A` when `ls` has no output
2016-11-01 12:51:04 +01:00
Vladimir Iakovlev
6f0d1e287d #571: Don't put empty string in history in zsh 2016-10-31 18:52:48 +01:00
Joseph Frazier
756044e087 Suggest ls -A when ls has no output 2016-10-31 13:49:38 -04:00
Vladimir Iakovlev
ddd8788353 #571: always honor alter_history setting in zsh 2016-10-31 12:57:31 +01:00
Vladimir Iakovlev
76c0e7bc70 Merge pull request #571 from josephfrazier/bash-honor-alter-history
bash: always honor alter_history setting
2016-10-31 12:56:31 +01:00
Vladimir Iakovlev
4865bdd81f Merge pull request #569 from scorphus/rebase-skip
#N/A: Add `git_rebase_merge_dir` rule
2016-10-31 12:55:05 +01:00
Joseph Frazier
fa169c686c test_git_flag_after_filename.py: dedupe test commands 2016-10-31 00:22:24 -04:00
Joseph Frazier
9cae0bffff git_flag_after_filename: fix flake8 errors
These were found by creating a `.flake8` file containing:

    [flake8]
    ignore = E501,W503
    exclude = venv

then running:

    flake8 $(git diff master... --name-only)

See https://github.com/nvbn/thefuck/pull/563 for running `flake8` in CI
2016-10-31 00:22:24 -04:00
Joseph Frazier
b519d317f7 bash: always honor alter_history setting
This ensures that even if the command suggested and run by `thefuck`
fails, it will still be added to the history, allowing the user to tweak
it further (or run `fuck` again) if desired.

Note that the fish shell appears to already behave this way.
2016-10-30 23:17:52 -04:00
Joseph Frazier
5b420204c9 git: fix fatal: bad flag '...' after filename
For example:

    $ git log README.md -p
    fatal: bad flag '-p' used after filename
    $ fuck
    git log -p README.md [enter/↑/↓/ctrl+c]
    Aborted

    $ git log -p README.md --name-only
    fatal: bad flag '--name-only' used after filename
    $ fuck
    git log -p --name-only README.md [enter/↑/↓/ctrl+c]
    Aborted

    $ git log README.md -p CONTRIBUTING.md
    fatal: bad flag '-p' used after filename
    $ fuck
    git log -p README.md CONTRIBUTING.md [enter/↑/↓/ctrl+c]
2016-10-30 21:40:25 -04:00
Pablo Santiago Blum de Aguiar
07005b591a #N/A: Add git_rebase_merge_dir rule 2016-10-30 20:30:26 -02:00
Vladimir Iakovlev
cb99e42e02 Merge pull request #567 from scorphus/git-rm-local-modifications
#N/A: Add `git_rm_local_modifications` rule
2016-10-30 19:51:20 +01:00
Vladimir Iakovlev
51f77964c6 Merge pull request #568 from scorphus/osx-brew-install
#N/A: Do not fail if formula is already installed
2016-10-30 19:50:02 +01:00
Pablo Santiago Blum de Aguiar
30b1c44f91 #N/A: Do not fail if formula is already installed 2016-10-30 15:02:12 -02:00
Pablo Santiago Blum de Aguiar
af28f0334a #N/A: Add git_rm_local_modifications rule 2016-10-29 17:51:55 -02:00
Vladimir Iakovlev
5ee5439c1e #565: Refine git_push rule 2016-10-08 12:24:48 +02:00
Vladimir Iakovlev
cf006dac2c Merge branch 'master' into josephfrazier-git-push-u
# Conflicts:
#	thefuck/rules/git_push.py
2016-10-08 12:20:23 +02:00
Vladimir Iakovlev
5b535077bf #N/A: Stop changing Command inside rules 2016-10-08 12:18:33 +02:00
Joseph Frazier
dda9d55989 Add flake8 instructions to README.md
Also add flake8 to requirements.txt so that it will be installed by:

    pip install -r requirements.txt
2016-10-07 22:27:07 -04:00
Joseph Frazier
f0b9c7cb67 Ignore remaining flake8 rules and exclude ./venv/
This should fix the builds.
2016-10-07 22:27:07 -04:00
Joseph Frazier
521eb03d7a Fix flake8 errors: E127 continuation line over-indented for visual indent 2016-10-07 22:27:07 -04:00
Joseph Frazier
e0cab4fa1b Fix flake8 errors: E126 continuation line over-indented for hanging indent 2016-10-07 22:27:07 -04:00
Joseph Frazier
1b30c00546 Fix flake8 errors: E122 continuation line missing indentation or outdented 2016-10-07 22:27:07 -04:00
Joseph Frazier
a9d55e9c62 Fix flake8 errors: E128 continuation line under-indented for visual indent
See https://github.com/nvbn/thefuck/pull/563#discussion_r82492221
2016-10-07 22:26:57 -04:00
Vladimir Iakovlev
cf3acbfa2e Merge branch 'git-push-u' of https://github.com/josephfrazier/thefuck into josephfrazier-git-push-u 2016-10-07 10:40:02 +02:00
Vladimir Iakovlev
4d714994a3 Merge pull request #564 from josephfrazier/docker-python
Use official Python images for Docker tests
2016-10-07 10:38:49 +02:00
Vladimir Iakovlev
02f717a0e8 Merge pull request #562 from josephfrazier/man-help
Suggest `foo --help` when `man foo` shows no pages
2016-10-07 10:37:33 +02:00
Vladimir Iakovlev
8f4f2f03a7 Merge pull request #561 from josephfrazier/ag-literal
Suggest `ag -Q` when relevant
2016-10-07 10:35:34 +02:00
Joseph Frazier
432878bd77 Ignore flake8 errors with inline comments: W291 trailing whitespace
See https://github.com/PyCQA/pycodestyle/pull/243
2016-10-06 15:31:55 -04:00
Joseph Frazier
fb3d8d1e01 Ignore flake8 errors with inline comments: F401,F403 2016-10-06 15:31:55 -04:00
Joseph Frazier
c4c6f506f4 Ignore flake8 errors with inline comments: E402 module level import not at top of file
https://github.com/nvbn/thefuck/pull/563#discussion_r82105200
2016-10-06 15:31:55 -04:00
Joseph Frazier
725605cd20 Fix flake8 errors: E711 comparison to None should be 'if cond is not None:' 2016-10-06 15:31:55 -04:00
Joseph Frazier
797b42cfd7 Fix flake8 errors: E302 expected 2 blank lines, found 1 2016-10-06 15:31:55 -04:00
Joseph Frazier
37161832aa Fix flake8 errors: E123 closing bracket does not match indentation of opening bracket's line 2016-10-06 15:31:55 -04:00
Joseph Frazier
b221b04d0f Fix flake8 errors: F811 redefinition of unused... 2016-10-06 15:31:55 -04:00
Joseph Frazier
dcc13bd2d2 Fix flake8 errors: F841, E265
This commented-out test caused a couple flake8 errors, so get rid of it:
* F841 local variable 'cmd' is assigned to but never used
* E265 block comment should start with '# '

See https://github.com/nvbn/thefuck/pull/563#discussion_r82104360
2016-10-06 15:30:53 -04:00
Joseph Frazier
283eb09c19 Fix flake8 errors: E231 missing whitespace after ',' 2016-10-06 13:30:39 -04:00
Joseph Frazier
10d409e6e2 Fix flake8 errors: E225 missing whitespace around operator 2016-10-06 13:30:13 -04:00
Joseph Frazier
93302c74b5 Fix flake8 errors: E101/W191 indentation contains (mixed spaces and) tabs 2016-10-06 13:30:13 -04:00
Joseph Frazier
22b005cebb Fix flake8 errors: E731 do not assign a lambda expression, use a def 2016-10-06 13:30:05 -04:00
Joseph Frazier
feb36ede5c Fix suggestion for git push -u
This was broken by https://github.com/nvbn/thefuck/pull/559
2016-10-06 13:09:40 -04:00
Joseph Frazier
16a440cb9d test_zsh.py: use official python images, not ubuntu
This should help reduce build times.
2016-10-06 11:15:18 -04:00
Joseph Frazier
10b20574d1 test_tcsh.py: use official python images, not ubuntu
This should help reduce build times.
2016-10-06 11:12:38 -04:00
Joseph Frazier
91fceb401a test_fish.py: use official python images, not ubuntu
This should help reduce build times.
2016-10-06 11:09:19 -04:00
Joseph Frazier
4b79e23ba7 test_bash.py: use official python images, not ubuntu
This should help reduce build times.
2016-10-06 10:56:37 -04:00
Joseph Frazier
f915a6ed0c test_performance.py: use python:3 image, not ubuntu
This should help reduce build times.
2016-10-06 10:54:47 -04:00
Joseph Frazier
e9d29726bc Run flake8 using Travis CI 2016-10-05 11:00:39 -04:00
Joseph Frazier
a964af7e95 ag_literal.py: use endswith() rather than in
https://github.com/nvbn/thefuck/pull/561#discussion_r81898499
2016-10-05 10:55:49 -04:00
Joseph Frazier
77fc021a6c Refactor tests/rules/test_ag_literal.py
https://github.com/nvbn/thefuck/pull/561#discussion_r81894710
2016-10-05 10:52:24 -04:00
Joseph Frazier
4822ceb87a ag_literal.py: remove unused import (Flake8 F401)
https://github.com/nvbn/thefuck/pull/561#discussion_r81892699
2016-10-05 10:34:17 -04:00
Joseph Frazier
b2947aba8d test_ag_literal.py: Add blank line (PEP 8 E302)
https://github.com/nvbn/thefuck/pull/561#discussion_r81892174
2016-10-05 10:32:14 -04:00
Joseph Frazier
d2e0a19aae Add missing semicolon to aws_cli entry in README 2016-10-03 14:22:17 -04:00
Joseph Frazier
0c84eefa55 Don't suggest man 2/3 foo if no man pages exist
Suggest `foo --help` instead. However, if there are man pages, suggest
`foo --help` after `man 2/3 foo`

This addresses the comment in the previous commit message:

> However, in cases where multiple sections have man pages for `foo`,
> running `man foo` could bring up the "wrong" section of man pages.
> `man read` is an example of this, but that should probably be handled in
> a way that still suggests `foo --help` first when there are *no* man
> pages for `foo` in any section.
2016-10-03 14:10:42 -04:00
Joseph Frazier
8bd6c5da67 For man foo, try foo --help before man 3 foo
`man` without a section searches all sections, so having `foo --help`
suggested first makes more sense than adding a specific section. See
https://github.com/nvbn/thefuck/pull/562#issuecomment-251142710

However, in cases where multiple sections have man pages for `foo`,
running `man foo` could bring up the "wrong" section of man pages.
`man read` is an example of this, but that should probably be handled in
a way that still suggests `foo --help` first when there are *no* man
pages for `foo` in any section.

Closes https://github.com/nvbn/thefuck/issues/546
2016-10-03 12:03:57 -04:00
Vladimir Iakovlev
ce6b82c92d #560: Fix code style 2016-10-03 13:07:30 +02:00
Joseph Frazier
5dbbb3b1ed Add ... --help to man suggestions
This is along the lines of what @waldyrious suggested in
https://github.com/nvbn/thefuck/issues/546, but it just adds a new
suggestion rather than replacing the other ones.
2016-10-03 03:57:53 -04:00
Joseph Frazier
db4b37910d Suggest ag -Q when relevant
This detects when `ag` suggests the `-Q` option, and adds it.
2016-10-03 00:33:40 -04:00
Joseph Frazier
2b88ea11ea Suggest git diff --no-index when relevant
This makes it easier to use `git diff` on untracked files.
2016-10-03 00:05:01 -04:00
Vladimir Iakovlev
db7dffdb44 Merge pull request #559 from josephfrazier/git-push-explicit-upstream
Fix suggestions for `git push -u origin`
2016-10-02 17:21:53 +02:00
Vladimir Iakovlev
92f3c8fb52 Merge pull request #557 from OJFord/patch-1
Add sudo rule for Aura
2016-10-02 17:21:04 +02:00
Vladimir Iakovlev
7c4f0d2e55 Merge pull request #551 from scorphus/git-bisect-usage
#N/A: Add `git_bisect_usage` rule
2016-10-02 17:20:43 +02:00
Vladimir Iakovlev
d05eb0a6dc #552: Fix code style 2016-10-02 17:19:33 +02:00
Vladimir Iakovlev
cf352fd788 Merge branch 'remove-trailing-cedilla' of https://github.com/wikiti/thefuck into wikiti-remove-trailing-cedilla 2016-10-02 17:18:24 +02:00
Vladimir Iakovlev
3c1cce6bd2 Merge branch 'brew-link' of https://github.com/josephfrazier/thefuck into josephfrazier-brew-link
# Conflicts:
#	README.md
2016-10-02 17:17:15 +02:00
Vladimir Iakovlev
5d3a727d1a Merge pull request #555 from josephfrazier/brew-uninstall-force
Suggest `brew uninstall --force` when relevant
2016-10-02 17:14:54 +02:00
Vladimir Iakovlev
ea87d55771 Merge pull request #554 from JordonPhillips/aws-rule
Add new aws cli rule
2016-10-02 17:14:14 +02:00
Joseph Frazier
aa6b18d0ce Fix suggestions for git push -u origin
Resolves https://github.com/nvbn/thefuck/issues/558
2016-09-30 16:13:50 -04:00
Joseph Frazier
934eeaf4fc Test that git push -u origin still works
This was broken by https://github.com/nvbn/thefuck/pull/538
2016-09-30 16:11:46 -04:00
Ollie Ford
3ad8d52a84 Add sudo rule for Aura
When installing from Arch User Repository without root:
    aura >>= You have to use `sudo` for that.

This commit adds the slightly more general, but unambiguous, "use `sudo`".

This commit closes #543.
2016-09-30 20:32:40 +01:00
Joseph Frazier
bb5c7c576f Suggest brew link --overwrite --dry-run when relevant
This makes it easier to see which files would be overwritten by
`brew link --overwrite`
2016-09-30 15:31:25 -04:00
Joseph Frazier
17c3935078 Test brew uninstall --force suggestion 2016-09-29 17:44:07 -04:00
Joseph Frazier
a734b94fec Suggest brew uninstall --force when relevant
Resolves https://github.com/nvbn/thefuck/issues/553
2016-09-29 17:26:20 -04:00
JordonPhillips
7bf405e9c3 Add aws cli rule
This rule corrects spelling mistakes for aws cli commands and
subcommands.
2016-09-29 14:22:08 -07:00
Daniel Herzog
c3bcdd7dee Update README 2016-09-29 21:43:02 +01:00
Daniel Herzog
ad53023860 Fix encoding for Python 2.7 2016-09-29 21:41:50 +01:00
Daniel
8938323229 Fix encoding 2016-09-29 11:06:56 +01:00
Daniel
92133f77d6 Add test file 2016-09-29 10:44:17 +01:00
Daniel
64eaf96eb8 Add rule 2016-09-29 10:34:41 +01:00
Pablo Santiago Blum de Aguiar
c9264aff10 #N/A: Add git_bisect_usage rule 2016-09-27 19:42:01 -03:00
Vladimir Iakovlev
9660ec7813 Merge branch 'juzim-git-pull-uncommitted-changes' 2016-09-20 00:28:51 +02:00
Vladimir Iakovlev
9ac47d8f78 #550: Use shell.and_ 2016-09-20 00:28:09 +02:00
Julian Zimmermann
6e2b82911f Removed linebreak 2016-09-19 13:07:48 +02:00
Julian Zimmermann
af9d34c299 Added rule that stashes changed files before pulling and pops them afterwards. 2016-09-19 12:52:23 +02:00
Vladimir Iakovlev
bcc11219e6 Merge pull request #545 from waldyrious/patch-1
readme: add -H flag to second sudo pip command
2016-09-06 22:38:17 +02:00
Waldir Pimenta
495a66088b readme: add -H flag to second sudo pip command 2016-09-06 17:01:09 +01:00
Vladimir Iakovlev
4fe64e3dfa #N/A: Match git_add only if pathspec exists 2016-08-23 13:03:49 +03:00
Vladimir Iakovlev
cae76eb55f Merge branch 'kthrift-fix/prevent-cwd-tilde-dir-creation' 2016-08-22 05:45:41 +03:00
Vladimir Iakovlev
afd2ed4e51 #540: Fix code style, add test 2016-08-22 05:45:27 +03:00
Vladimir Iakovlev
1a4d74d487 Merge branch 'fix/prevent-cwd-tilde-dir-creation' of https://github.com/kthrift/thefuck into kthrift-fix/prevent-cwd-tilde-dir-creation 2016-08-22 05:20:29 +03:00
Kyle Thrift
0bd3e85e08 fix: new config dirs created in $HOME/.config/thefuck instead of $CWD
fix: use correct path in warning message when XDG_CONFIG_HOME defined
2016-08-21 15:59:16 -04:00
Vladimir Iakovlev
faeeef7666 Merge pull request #539 from blahgeek/master
prevent infinity loop while detecting shell
2016-08-21 15:22:36 +08:00
Vladimir Iakovlev
4d65d6a1df Merge pull request #538 from lukechilds/git-push-with-args
Preserve args for git_push
2016-08-21 15:20:54 +08:00
BlahGeek
cfa51506fb prevent infinity loop while detecting shell
In OS X, Process(pid=0).parent() == Process(pid=0)
2016-08-20 12:00:03 +08:00
Luke Childs
5df350254e Check arguments are preserved in git_push 2016-08-19 22:29:43 +01:00
Luke Childs
612c393ec4 Check git_push matches without specifying a branch 2016-08-19 22:19:09 +01:00
Luke Childs
4d89b3499e Preserve args for git_push 2016-08-19 22:08:30 +01:00
Vladimir Iakovlev
070bb2ff28 #N/A: Show deprecation warning when ~/.thefuck used 2016-08-14 20:02:33 +03:00
Vladimir Iakovlev
71025dff17 #N/A: Monkeypatch old pathlib even on unix 2016-08-14 15:32:53 +03:00
Vladimir Iakovlev
621b455334 #N/A: Monkeypatch pathlib on windows 2016-08-14 15:15:03 +03:00
Vladimir Iakovlev
176924c18d #N/A: Move imports from pathlib/pathlib2 to utils 2016-08-14 15:01:00 +03:00
Vladimir Iakovlev
1f75fc1ea9 #N/A: Remove deprecated thefuck-alias entry point 2016-08-14 14:43:13 +03:00
Vladimir Iakovlev
46cb87615e #N/A: Remove old-style rules support 2016-08-14 14:37:32 +03:00
162 changed files with 3253 additions and 636 deletions

View File

@@ -2,6 +2,9 @@ language: python
sudo: false sudo: false
matrix: matrix:
include: include:
- os: linux
dist: trusty
python: "3.6"
- os: linux - os: linux
dist: trusty dist: trusty
python: "3.5" python: "3.5"
@@ -29,7 +32,7 @@ addons:
- python3-commandnotfound - python3-commandnotfound
before_install: before_install:
- if [[ $TRAVIS_OS_NAME == "osx" ]]; then brew update ; fi - if [[ $TRAVIS_OS_NAME == "osx" ]]; then brew update ; fi
- if [[ $TRAVIS_OS_NAME == "osx" ]]; then brew install $FORMULA; fi - if [[ $TRAVIS_OS_NAME == "osx" ]]; then if brew ls --versions $FORMULA; then brew upgrade $FORMULA || echo Python is up to date; else brew install $FORMULA; fi; fi
- if [[ $TRAVIS_OS_NAME == "osx" ]]; then virtualenv venv -p $FORMULA; fi - if [[ $TRAVIS_OS_NAME == "osx" ]]; then virtualenv venv -p $FORMULA; fi
- if [[ $TRAVIS_OS_NAME == "osx" ]]; then source venv/bin/activate; fi - if [[ $TRAVIS_OS_NAME == "osx" ]]; then source venv/bin/activate; fi
- pip install -U pip - pip install -U pip
@@ -39,9 +42,10 @@ install:
- python setup.py develop - python setup.py develop
- rm -rf build - rm -rf build
script: script:
- flake8
- export COVERAGE_PYTHON_VERSION=python-${TRAVIS_PYTHON_VERSION:0:1} - export COVERAGE_PYTHON_VERSION=python-${TRAVIS_PYTHON_VERSION:0:1}
- export RUN_TESTS="coverage run --source=thefuck,tests -m py.test -v --capture=sys tests" - export RUN_TESTS="coverage run --source=thefuck,tests -m py.test -v --capture=sys tests"
- if [[ $TRAVIS_PYTHON_VERSION == 3.5 && $TRAVIS_OS_NAME != "osx" ]]; then $RUN_TESTS --enable-functional; fi - if [[ $TRAVIS_PYTHON_VERSION == 3.6 && $TRAVIS_OS_NAME != "osx" ]]; then $RUN_TESTS --enable-functional; fi
- if [[ $TRAVIS_PYTHON_VERSION != 3.5 || $TRAVIS_OS_NAME == "osx" ]]; then $RUN_TESTS; fi - if [[ $TRAVIS_PYTHON_VERSION != 3.6 || $TRAVIS_OS_NAME == "osx" ]]; then $RUN_TESTS; fi
after_success: after_success:
- coveralls - if [[ $TRAVIS_PYTHON_VERSION == 3.6 && $TRAVIS_OS_NAME != "osx" ]]; then coveralls; fi

View File

@@ -1 +1,2 @@
include LICENSE.md include LICENSE.md
include fastentrypoints.py

View File

@@ -4,6 +4,8 @@ Magnificent app which corrects your previous console command,
inspired by a [@liamosaur](https://twitter.com/liamosaur/) inspired by a [@liamosaur](https://twitter.com/liamosaur/)
[tweet](https://twitter.com/liamosaur/status/506975850596536320). [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] [![gif with examples][examples-link]][examples-link]
Few more examples: Few more examples:
@@ -103,17 +105,16 @@ brew install thefuck
``` ```
On Ubuntu you can install `The Fuck` with: On Ubuntu you can install `The Fuck` with:
```bash ```bash
sudo apt update sudo apt update
sudo apt install python3-dev python3-pip sudo apt install python3-dev python3-pip
sudo -H pip3 install thefuck sudo pip3 install thefuck
``` ```
On other systems you can install `The Fuck` with `pip`: On other systems you can install `The Fuck` with `pip`:
```bash ```bash
sudo -H pip install thefuck pip install thefuck
``` ```
[Or using an OS package manager (OS X, Ubuntu, Arch).](https://github.com/nvbn/thefuck/wiki/Installation) [Or using an OS package manager (OS X, Ubuntu, Arch).](https://github.com/nvbn/thefuck/wiki/Installation)
@@ -121,9 +122,9 @@ sudo -H pip install thefuck
You should place this command in your `.bash_profile`, `.bashrc`, `.zshrc` or other startup script: You should place this command in your `.bash_profile`, `.bashrc`, `.zshrc` or other startup script:
```bash ```bash
eval "$(thefuck --alias)" eval $(thefuck --alias)
# You can use whatever you want as an alias, like for Mondays: # You can use whatever you want as an alias, like for Mondays:
eval "$(thefuck --alias FUCK)" eval $(thefuck --alias FUCK)
``` ```
[Or in your shell config (Bash, Zsh, Fish, Powershell, tcsh).](https://github.com/nvbn/thefuck/wiki/Shell-aliases) [Or in your shell config (Bash, Zsh, Fish, Powershell, tcsh).](https://github.com/nvbn/thefuck/wiki/Shell-aliases)
@@ -131,11 +132,22 @@ eval "$(thefuck --alias FUCK)"
Changes will be available only in a new shell session. 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`). To make them available immediately, run `source ~/.bashrc` (or your shell config file like `.zshrc`).
If you want to run fixed command without confirmation you can use `-y` option:
```bash
fuck -y
```
If you want to fix commands recursively until success you can use `-r` option:
```bash
fuck -r
```
## Update ## Update
```bash ```bash
sudo pip install thefuck --upgrade pip install thefuck --upgrade
``` ```
**Aliases changed in 1.34.** **Aliases changed in 1.34.**
@@ -145,6 +157,8 @@ sudo pip install thefuck --upgrade
The Fuck tries to match a rule for the previous command, creates a new command The Fuck tries to match a rule for the previous command, creates a new command
using the matched rule and runs it. Rules enabled by default are as follows: using the matched rule and runs it. Rules enabled by default are as follows:
* `ag_literal` &ndash; adds `-Q` to `ag` when suggested;
* `aws_cli` &ndash; fixes misspelled commands like `aws dynamdb scan`;
* `cargo` &ndash; runs `cargo build` instead of `cargo`; * `cargo` &ndash; runs `cargo build` instead of `cargo`;
* `cargo_no_command` &ndash; fixes wrongs commands like `cargo buid`; * `cargo_no_command` &ndash; fixes wrongs commands like `cargo buid`;
* `cd_correction` &ndash; spellchecks and correct failed cd commands; * `cd_correction` &ndash; spellchecks and correct failed cd commands;
@@ -163,23 +177,35 @@ using the matched rule and runs it. Rules enabled by default are as follows:
* `fab_command_not_found` &ndash; fix misspelled fabric commands; * `fab_command_not_found` &ndash; fix misspelled fabric commands;
* `fix_alt_space` &ndash; replaces Alt+Space with Space character; * `fix_alt_space` &ndash; replaces Alt+Space with Space character;
* `fix_file` &ndash; opens a file with an error in your `$EDITOR`; * `fix_file` &ndash; opens a file with an error in your `$EDITOR`;
* `gem_unknown_command` &ndash; fixes wrong `gem` commands;
* `git_add` &ndash; fixes *"pathspec 'foo' did not match any file(s) known to git."*; * `git_add` &ndash; fixes *"pathspec 'foo' did not match any file(s) known to git."*;
* `git_add_force` &ndash; adds `--force` to `git add <pathspec>...` when paths are .gitignore'd;
* `git_bisect_usage` &ndash; fixes `git bisect strt`, `git bisect goood`, `git bisect rset`, etc. when bisecting;
* `git_branch_delete` &ndash; changes `git branch -d` to `git branch -D`; * `git_branch_delete` &ndash; changes `git branch -d` to `git branch -D`;
* `git_branch_exists` &ndash; offers `git branch -d foo`, `git branch -D foo` or `git checkout foo` when creating a branch that already exists; * `git_branch_exists` &ndash; offers `git branch -d foo`, `git branch -D foo` or `git checkout foo` when creating a branch that already exists;
* `git_branch_list` &ndash; catches `git branch list` in place of `git branch` and removes created branch; * `git_branch_list` &ndash; catches `git branch list` in place of `git branch` and removes created branch;
* `git_checkout` &ndash; fixes branch name or creates new branch; * `git_checkout` &ndash; fixes branch name or creates new branch;
* `git_diff_no_index` &ndash; adds `--no-index` to previous `git diff` on untracked files;
* `git_diff_staged` &ndash; adds `--staged` to previous `git diff` with unexpected output; * `git_diff_staged` &ndash; adds `--staged` to previous `git diff` with unexpected output;
* `git_fix_stash` &ndash; fixes `git stash` commands (misspelled subcommand and missing `save`); * `git_fix_stash` &ndash; fixes `git stash` commands (misspelled subcommand and missing `save`);
* `git_flag_after_filename` &ndash; fixes `fatal: bad flag '...' after filename`
* `git_help_aliased` &ndash; fixes `git help <alias>` commands replacing <alias> with the aliased command; * `git_help_aliased` &ndash; fixes `git help <alias>` commands replacing <alias> with the aliased command;
* `git_not_command` &ndash; fixes wrong git commands like `git brnch`; * `git_not_command` &ndash; fixes wrong git commands like `git brnch`;
* `git_pull` &ndash; sets upstream before executing previous `git pull`; * `git_pull` &ndash; sets upstream before executing previous `git pull`;
* `git_pull_clone` &ndash; clones instead of pulling when the repo does not exist; * `git_pull_clone` &ndash; clones instead of pulling when the repo does not exist;
* `git_pull_uncommitted_changes` &ndash; stashes changes before pulling and pops them afterwards;
* `git_push` &ndash; adds `--set-upstream origin $branch` to previous failed `git push`; * `git_push` &ndash; adds `--set-upstream origin $branch` to previous failed `git push`;
* `git_push_pull` &ndash; runs `git pull` when `push` was rejected; * `git_push_pull` &ndash; runs `git pull` when `push` was rejected;
* `git_push_without_commits` &ndash; Creates an initial commit if you forget and only `git add .`, when setting up a new project;
* `git_rebase_no_changes` &ndash; runs `git rebase --skip` instead of `git rebase --continue` when there are no changes; * `git_rebase_no_changes` &ndash; runs `git rebase --skip` instead of `git rebase --continue` when there are no changes;
* `git_rm_local_modifications` &ndash; adds `-f` or `--cached` when you try to `rm` a locally modified file;
* `git_rm_recursive` &ndash; adds `-r` when you try to `rm` a directory; * `git_rm_recursive` &ndash; adds `-r` when you try to `rm` a directory;
* `git_rm_staged` &ndash; adds `-f` or `--cached` when you try to `rm` a file with staged changes
* `git_rebase_merge_dir` &ndash; offers `git rebase (--continue | --abort | --skip)` or removing the `.git/rebase-merge` dir when a rebase is in progress;
* `git_remote_seturl_add` &ndash; runs `git remote add` when `git remote set_url` on nonexistant remote; * `git_remote_seturl_add` &ndash; runs `git remote add` when `git remote set_url` on nonexistant remote;
* `git_stash` &ndash; stashes you local modifications before rebasing or switching branch; * `git_stash` &ndash; stashes your local modifications before rebasing or switching branch;
* `git_stash_pop` &ndash; adds your local modifications before popping stash, then resets;
* `git_tag_force` &ndash; adds `--force` to `git tag <tagname>` when the tag already exists;
* `git_two_dashes` &ndash; adds a missing dash to commands like `git commit -amend` or `git rebase -continue`; * `git_two_dashes` &ndash; adds a missing dash to commands like `git commit -amend` or `git rebase -continue`;
* `go_run` &ndash; appends `.go` extension when compiling/running Go programs; * `go_run` &ndash; appends `.go` extension when compiling/running Go programs;
* `gradle_no_task` &ndash; fixes not found or ambiguous `gradle` task; * `gradle_no_task` &ndash; fixes not found or ambiguous `gradle` task;
@@ -191,15 +217,19 @@ using the matched rule and runs it. Rules enabled by default are as follows:
* `has_exists_script` &ndash; prepends `./` when script/binary exists; * `has_exists_script` &ndash; prepends `./` when script/binary exists;
* `heroku_not_command` &ndash; fixes wrong `heroku` commands like `heroku log`; * `heroku_not_command` &ndash; fixes wrong `heroku` commands like `heroku log`;
* `history` &ndash; tries to replace command with most similar command from history; * `history` &ndash; tries to replace command with most similar command from history;
* `hostscli` &ndash; tries to fix `hostscli` usage;
* `ifconfig_device_not_found` &ndash; fixes wrong device names like `wlan0` to `wlp2s0`;
* `java` &ndash; removes `.java` extension when running Java programs; * `java` &ndash; removes `.java` extension when running Java programs;
* `javac` &ndash; appends missing `.java` when compiling Java files; * `javac` &ndash; appends missing `.java` when compiling Java files;
* `lein_not_task` &ndash; fixes wrong `lein` tasks like `lein rpl`; * `lein_not_task` &ndash; fixes wrong `lein` tasks like `lein rpl`;
* `ln_no_hard_link` &ndash; catches hard link creation on directories, suggest symbolic link; * `ln_no_hard_link` &ndash; catches hard link creation on directories, suggest symbolic link;
* `ln_s_order` &ndash; fixes `ln -s` arguments order; * `ln_s_order` &ndash; fixes `ln -s` arguments order;
* `ls_all` &ndash; adds `-A` to `ls` when output is empty;
* `ls_lah` &ndash; adds `-lah` to `ls`; * `ls_lah` &ndash; adds `-lah` to `ls`;
* `man` &ndash; changes manual section; * `man` &ndash; changes manual section;
* `man_no_space` &ndash; fixes man commands without spaces, for example `mandiff`; * `man_no_space` &ndash; fixes man commands without spaces, for example `mandiff`;
* `mercurial` &ndash; fixes wrong `hg` commands; * `mercurial` &ndash; fixes wrong `hg` commands;
* `missing_space_before_subcommand` &ndash; fixes command with missing space like `npminstall`;
* `mkdir_p` &ndash; adds `-p` when you trying to create directory without parent; * `mkdir_p` &ndash; adds `-p` when you trying to create directory without parent;
* `mvn_no_command` &ndash; adds `clean package` to `mvn`; * `mvn_no_command` &ndash; adds `clean package` to `mvn`;
* `mvn_unknown_lifecycle_phase` &ndash; fixes misspelled lifecycle phases with `mvn`; * `mvn_unknown_lifecycle_phase` &ndash; fixes misspelled lifecycle phases with `mvn`;
@@ -214,12 +244,16 @@ using the matched rule and runs it. Rules enabled by default are as follows:
* `python_command` &ndash; prepends `python` when you trying to run not executable/without `./` python script; * `python_command` &ndash; prepends `python` when you trying to run not executable/without `./` python script;
* `python_execute` &ndash; appends missing `.py` when executing Python files; * `python_execute` &ndash; appends missing `.py` when executing Python files;
* `quotation_marks` &ndash; fixes uneven usage of `'` and `"` when containing args'; * `quotation_marks` &ndash; fixes uneven usage of `'` and `"` when containing args';
* `path_from_history` &ndash; replaces not found path with similar absolute path from history;
* `react_native_command_unrecognized` &ndash; fixes unrecognized `react-native` commands; * `react_native_command_unrecognized` &ndash; fixes unrecognized `react-native` commands;
* `remove_trailing_cedilla` &ndash; remove trailling cedillas `ç`, a common typo for european keyboard layouts;
* `rm_dir` &ndash; adds `-rf` when you trying to remove directory; * `rm_dir` &ndash; adds `-rf` when you trying to remove directory;
* `scm_correction` &ndash; corrects wrong scm like `hg log` to `git log`;
* `sed_unterminated_s` &ndash; adds missing '/' to `sed`'s `s` commands; * `sed_unterminated_s` &ndash; adds missing '/' to `sed`'s `s` commands;
* `sl_ls` &ndash; changes `sl` to `ls`; * `sl_ls` &ndash; changes `sl` to `ls`;
* `ssh_known_hosts` &ndash; removes host from `known_hosts` on warning; * `ssh_known_hosts` &ndash; removes host from `known_hosts` on warning;
* `sudo` &ndash; prepends `sudo` to previous command if it failed because of permissions; * `sudo` &ndash; prepends `sudo` to previous command if it failed because of permissions;
* `sudo_command_from_user_path` &ndash; runs commands from users `$PATH` with `sudo`;
* `switch_lang` &ndash; switches command from your local layout to en; * `switch_lang` &ndash; switches command from your local layout to en;
* `systemctl` &ndash; correctly orders parameters of confusing `systemctl`; * `systemctl` &ndash; correctly orders parameters of confusing `systemctl`;
* `test.py` &ndash; runs `py.test` instead of `test.py`; * `test.py` &ndash; runs `py.test` instead of `test.py`;
@@ -231,6 +265,10 @@ using the matched rule and runs it. Rules enabled by default are as follows:
* `vagrant_up` &ndash; starts up the vagrant instance; * `vagrant_up` &ndash; starts up the vagrant instance;
* `whois` &ndash; fixes `whois` command; * `whois` &ndash; fixes `whois` command;
* `workon_doesnt_exists` &ndash; fixes `virtualenvwrapper` env name os suggests to create new. * `workon_doesnt_exists` &ndash; fixes `virtualenvwrapper` env name os suggests to create new.
* `yarn_alias` &ndash; fixes aliased `yarn` commands like `yarn ls`;
* `yarn_command_not_found` &ndash; fixes misspelled `yarn` commands;
* `yarn_command_replaced` &ndash; fixes replaced `yarn` commands;
* `yarn_help` &ndash; makes it easier to open `yarn` documentation;
Enabled by default only on specific platforms: Enabled by default only on specific platforms:
@@ -238,6 +276,8 @@ Enabled by default only on specific platforms:
* `apt_get_search` &ndash; changes trying to search using `apt-get` with searching using `apt-cache`; * `apt_get_search` &ndash; changes trying to search using `apt-get` with searching using `apt-cache`;
* `apt_invalid_operation` &ndash; fixes invalid `apt` and `apt-get` calls, like `apt-get isntall vim`; * `apt_invalid_operation` &ndash; fixes invalid `apt` and `apt-get` calls, like `apt-get isntall vim`;
* `brew_install` &ndash; fixes formula name for `brew install`; * `brew_install` &ndash; fixes formula name for `brew install`;
* `brew_link` &ndash; adds `--overwrite --dry-run` if linking fails;
* `brew_uninstall` &ndash; adds `--force` to `brew uninstall` if multiple versions were installed;
* `brew_unknown_command` &ndash; fixes wrong brew commands, for example `brew docto/brew doctor`; * `brew_unknown_command` &ndash; fixes wrong brew commands, for example `brew docto/brew doctor`;
* `brew_update_formula` &ndash; turns `brew update <formula>` into `brew upgrade <formula>`; * `brew_update_formula` &ndash; turns `brew update <formula>` into `brew upgrade <formula>`;
* `brew_upgrade` &ndash; appends `--all` to `brew upgrade` as per Homebrew's new behaviour; * `brew_upgrade` &ndash; appends `--all` to `brew upgrade` as per Homebrew's new behaviour;
@@ -266,7 +306,9 @@ side_effect(old_command: Command, fixed_command: str) -> None
``` ```
and optional `enabled_by_default`, `requires_output` and `priority` variables. and optional `enabled_by_default`, `requires_output` and `priority` variables.
`Command` has three attributes: `script`, `stdout` and `stderr`. `Command` has four attributes: `script`, `stdout`, `stderr` and `script_parts`.
Rule shouldn't change `Command`.
*Rules api changed in 3.0:* For accessing settings in rule you need to import it with `from thefuck.conf import settings`. *Rules api changed in 3.0:* For accessing settings in rule you need to import it with `from thefuck.conf import settings`.
`settings` is a special object filled with `~/.config/thefuck/settings.py` and values from env ([see more below](#settings)). `settings` is a special object filled with `~/.config/thefuck/settings.py` and values from env ([see more below](#settings)).
@@ -355,6 +397,23 @@ export THEFUCK_PRIORITY='no_command=9999:apt_get=100'
export THEFUCK_HISTORY_LIMIT='2000' 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 ## Developing
Install `The Fuck` for development: Install `The Fuck` for development:
@@ -364,6 +423,12 @@ pip install -r requirements.txt
python setup.py develop python setup.py develop
``` ```
Run code style checks:
```bash
flake8
```
Run unit tests: Run unit tests:
```bash ```bash
@@ -389,12 +454,13 @@ Project License can be found [here](LICENSE.md).
[version-badge]: https://img.shields.io/pypi/v/thefuck.svg?label=version [version-badge]: https://img.shields.io/pypi/v/thefuck.svg?label=version
[version-link]: https://pypi.python.org/pypi/thefuck/ [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 [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 [appveyor-link]: https://ci.appveyor.com/project/nvbn/thefuck
[coverage-badge]: https://img.shields.io/coveralls/nvbn/thefuck.svg [coverage-badge]: https://img.shields.io/coveralls/nvbn/thefuck.svg
[coverage-link]: https://coveralls.io/github/nvbn/thefuck [coverage-link]: https://coveralls.io/github/nvbn/thefuck
[license-badge]: https://img.shields.io/badge/license-MIT-007EC7.svg [license-badge]: https://img.shields.io/badge/license-MIT-007EC7.svg
[examples-link]: https://raw.githubusercontent.com/nvbn/thefuck/master/example.gif [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/ [homebrew]: http://brew.sh/

View File

@@ -6,6 +6,7 @@ environment:
- PYTHON: "C:/Python33" - PYTHON: "C:/Python33"
- PYTHON: "C:/Python34" - PYTHON: "C:/Python34"
- PYTHON: "C:/Python35" - PYTHON: "C:/Python35"
- PYTHON: "C:/Python36"
init: init:
- "ECHO %PYTHON%" - "ECHO %PYTHON%"
@@ -19,4 +20,5 @@ install:
- "%PYTHON%/Scripts/pip.exe install -U -r requirements.txt" - "%PYTHON%/Scripts/pip.exe install -U -r requirements.txt"
test_script: test_script:
- "%PYTHON%/python.exe -m flake8"
- "%PYTHON%/Scripts/py.test.exe -sv" - "%PYTHON%/Scripts/py.test.exe -sv"

BIN
example_instant_mode.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 535 KiB

110
fastentrypoints.py Normal file
View 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__)

View File

@@ -1,4 +1,5 @@
pip pip
flake8
pytest pytest
mock mock
pytest-mock pytest-mock

View File

@@ -3,6 +3,8 @@ from setuptools import setup, find_packages
import pkg_resources import pkg_resources
import sys import sys
import os import os
import fastentrypoints
try: try:
if int(pkg_resources.get_distribution("pip").version.split('.')[0]) < 6: if int(pkg_resources.get_distribution("pip").version.split('.')[0]) < 6:
@@ -29,10 +31,11 @@ elif (3, 0) < version < (3, 3):
' ({}.{} detected).'.format(*version)) ' ({}.{} detected).'.format(*version))
sys.exit(-1) sys.exit(-1)
VERSION = '3.11' VERSION = '3.23'
install_requires = ['psutil', 'colorama', 'six', 'decorator'] install_requires = ['psutil', 'colorama', 'six', 'decorator', 'pyte']
extras_require = {':python_version<"3.4"': ['pathlib2'], extras_require = {':python_version<"3.4"': ['pathlib2'],
':python_version<"3.3"': ['backports.shutil_get_terminal_size'],
":sys_platform=='win32'": ['win_unicode_console']} ":sys_platform=='win32'": ['win_unicode_console']}
setup(name='thefuck', setup(name='thefuck',
@@ -51,5 +54,4 @@ setup(name='thefuck',
extras_require=extras_require, extras_require=extras_require,
entry_points={'console_scripts': [ entry_points={'console_scripts': [
'thefuck = thefuck.main:main', 'thefuck = thefuck.main:main',
'thefuck-alias = thefuck.main:print_alias', 'fuck = thefuck.not_configured:main']})
'fuck = thefuck.main:how_to_configure_alias']})

View File

@@ -1,16 +1,14 @@
try: import os
from pathlib import Path
except ImportError:
from pathlib2 import Path
import pytest import pytest
from thefuck import shells from thefuck import shells
from thefuck import conf, const from thefuck import conf, const
from thefuck.system import Path
shells.shell = shells.Generic() shells.shell = shells.Generic()
def pytest_addoption(parser): def pytest_addoption(parser):
"""Adds `--run-without-docker` argument.""" """Adds `--enable-functional` argument."""
group = parser.getgroup("thefuck") group = parser.getgroup("thefuck")
group.addoption('--enable-functional', action="store_true", default=False, group.addoption('--enable-functional', action="store_true", default=False,
help="Enable functional tests") help="Enable functional tests")
@@ -59,7 +57,13 @@ def set_shell(monkeypatch, request):
def _set(cls): def _set(cls):
shell = cls() shell = cls()
monkeypatch.setattr('thefuck.shells.shell', shell) monkeypatch.setattr('thefuck.shells.shell', shell)
request.addfinalizer()
return shell return shell
return _set return _set
@pytest.fixture(autouse=True)
def os_environ(monkeypatch):
env = {'PATH': os.environ['PATH']}
monkeypatch.setattr('os.environ', env)
return env

View File

@@ -81,6 +81,5 @@ def without_confirmation(proc, TIMEOUT):
def how_to_configure(proc, TIMEOUT): def how_to_configure(proc, TIMEOUT):
proc.sendline(u'unalias fuck')
proc.sendline(u'fuck') proc.sendline(u'fuck')
assert proc.expect([TIMEOUT, u"alias isn't configured"]) assert proc.expect([TIMEOUT, u"alias isn't configured"])

View File

@@ -3,18 +3,11 @@ from tests.functional.plots import with_confirmation, without_confirmation, \
refuse_with_confirmation, history_changed, history_not_changed, \ refuse_with_confirmation, history_changed, history_not_changed, \
select_command_with_arrows, how_to_configure select_command_with_arrows, how_to_configure
containers = ((u'thefuck/ubuntu-python3-bash', containers = ((u'thefuck/python3-bash',
u'''FROM ubuntu:latest u'FROM python:3',
RUN apt-get update
RUN apt-get install -yy python3 python3-pip python3-dev git
RUN pip3 install -U setuptools
RUN ln -s /usr/bin/pip3 /usr/bin/pip''',
u'bash'), u'bash'),
(u'thefuck/ubuntu-python2-bash', (u'thefuck/python2-bash',
u'''FROM ubuntu:latest u'FROM python:2',
RUN apt-get update
RUN apt-get install -yy python python-pip python-dev git
RUN pip2 install -U pip setuptools''',
u'bash')) u'bash'))
@@ -55,4 +48,5 @@ def test_without_confirmation(proc, TIMEOUT):
@pytest.mark.functional @pytest.mark.functional
def test_how_to_configure_alias(proc, TIMEOUT): def test_how_to_configure_alias(proc, TIMEOUT):
proc.sendline('unset -f fuck')
how_to_configure(proc, TIMEOUT) how_to_configure(proc, TIMEOUT)

View File

@@ -2,19 +2,20 @@ import pytest
from tests.functional.plots import with_confirmation, without_confirmation, \ from tests.functional.plots import with_confirmation, without_confirmation, \
refuse_with_confirmation, select_command_with_arrows refuse_with_confirmation, select_command_with_arrows
containers = (('thefuck/ubuntu-python3-fish', containers = (('thefuck/python3-fish',
u'''FROM ubuntu:latest u'''FROM python:3
# Use jessie-backports since it has the fish package. See here for details:
# https://github.com/tianon/docker-brew-debian/blob/88ae21052affd8a14553bb969f9d41c464032122/jessie/backports/Dockerfile
RUN awk '$1 ~ "^deb" { $3 = $3 "-backports"; print; exit }' /etc/apt/sources.list > /etc/apt/sources.list.d/backports.list
RUN apt-get update RUN apt-get update
RUN apt-get install -yy python3 python3-pip python3-dev fish git
RUN pip3 install -U setuptools
RUN ln -s /usr/bin/pip3 /usr/bin/pip
RUN apt-get install -yy fish''', RUN apt-get install -yy fish''',
u'fish'), u'fish'),
('thefuck/ubuntu-python2-fish', ('thefuck/python2-fish',
u'''FROM ubuntu:latest u'''FROM python:2
# Use jessie-backports since it has the fish package. See here for details:
# https://github.com/tianon/docker-brew-debian/blob/88ae21052affd8a14553bb969f9d41c464032122/jessie/backports/Dockerfile
RUN awk '$1 ~ "^deb" { $3 = $3 "-backports"; print; exit }' /etc/apt/sources.list > /etc/apt/sources.list.d/backports.list
RUN apt-get update RUN apt-get update
RUN apt-get install -yy python python-pip python-dev git
RUN pip2 install -U pip setuptools
RUN apt-get install -yy fish''', RUN apt-get install -yy fish''',
u'fish')) u'fish'))

View File

@@ -2,11 +2,7 @@ import pytest
import time import time
dockerfile = u''' dockerfile = u'''
FROM ubuntu:latest FROM python:3
RUN apt-get update
RUN apt-get install -yy python3 python3-pip python3-dev git
RUN pip3 install -U setuptools
RUN ln -s /usr/bin/pip3 /usr/bin/pip
RUN adduser --disabled-password --gecos '' test RUN adduser --disabled-password --gecos '' test
ENV SEED "{seed}" ENV SEED "{seed}"
WORKDIR /src WORKDIR /src
@@ -42,7 +38,7 @@ def plot(proc, TIMEOUT):
@pytest.mark.functional @pytest.mark.functional
@pytest.mark.benchmark(min_rounds=10) @pytest.mark.benchmark(min_rounds=10)
def test_performance(spawnu, TIMEOUT, benchmark): def test_performance(spawnu, TIMEOUT, benchmark):
proc = spawnu(u'thefuck/ubuntu-python3-bash-performance', proc = spawnu(u'thefuck/python3-bash-performance',
dockerfile, u'bash') dockerfile, u'bash')
proc.sendline(u'pip install /src') proc.sendline(u'pip install /src')
proc.sendline(u'su test') proc.sendline(u'su test')

View File

@@ -2,19 +2,14 @@ import pytest
from tests.functional.plots import with_confirmation, without_confirmation, \ from tests.functional.plots import with_confirmation, without_confirmation, \
refuse_with_confirmation, select_command_with_arrows refuse_with_confirmation, select_command_with_arrows
containers = (('thefuck/ubuntu-python3-tcsh', containers = (('thefuck/python3-tcsh',
u'''FROM ubuntu:latest u'''FROM python:3
RUN apt-get update RUN apt-get update
RUN apt-get install -yy python3 python3-pip python3-dev git
RUN pip3 install -U setuptools
RUN ln -s /usr/bin/pip3 /usr/bin/pip
RUN apt-get install -yy tcsh''', RUN apt-get install -yy tcsh''',
u'tcsh'), u'tcsh'),
('thefuck/ubuntu-python2-tcsh', ('thefuck/python2-tcsh',
u'''FROM ubuntu:latest u'''FROM python:2
RUN apt-get update RUN apt-get update
RUN apt-get install -yy python python-pip python-dev git
RUN pip2 install -U pip setuptools
RUN apt-get install -yy tcsh''', RUN apt-get install -yy tcsh''',
u'tcsh')) u'tcsh'))

View File

@@ -3,19 +3,14 @@ from tests.functional.plots import with_confirmation, without_confirmation, \
refuse_with_confirmation, history_changed, history_not_changed, \ refuse_with_confirmation, history_changed, history_not_changed, \
select_command_with_arrows, how_to_configure select_command_with_arrows, how_to_configure
containers = (('thefuck/ubuntu-python3-zsh', containers = (('thefuck/python3-zsh',
u'''FROM ubuntu:latest u'''FROM python:3
RUN apt-get update RUN apt-get update
RUN apt-get install -yy python3 python3-pip python3-dev git
RUN pip3 install -U setuptools
RUN ln -s /usr/bin/pip3 /usr/bin/pip
RUN apt-get install -yy zsh''', RUN apt-get install -yy zsh''',
u'zsh'), u'zsh'),
('thefuck/ubuntu-python2-zsh', ('thefuck/python2-zsh',
u'''FROM ubuntu:latest u'''FROM python:2
RUN apt-get update RUN apt-get update
RUN apt-get install -yy python python-pip python-dev git
RUN pip2 install -U pip setuptools
RUN apt-get install -yy zsh''', RUN apt-get install -yy zsh''',
u'zsh')) u'zsh'))
@@ -60,4 +55,5 @@ def test_without_confirmation(proc, TIMEOUT):
@pytest.mark.functional @pytest.mark.functional
def test_how_to_configure_alias(proc, TIMEOUT): def test_how_to_configure_alias(proc, TIMEOUT):
proc.sendline(u'unfunction fuck')
how_to_configure(proc, TIMEOUT) how_to_configure(proc, TIMEOUT)

View File

@@ -0,0 +1,25 @@
import pytest
from thefuck.rules.ag_literal import get_new_command, match
from tests.utils import Command
@pytest.fixture
def stderr():
return ('ERR: Bad regex! pcre_compile() failed at position 1: missing )\n'
'If you meant to search for a literal string, run ag with -Q\n')
@pytest.mark.parametrize('script', ['ag \('])
def test_match(script, stderr):
assert match(Command(script=script, stderr=stderr))
@pytest.mark.parametrize('script', ['ag foo'])
def test_not_match(script):
assert not match(Command(script=script))
@pytest.mark.parametrize('script, new_cmd', [
('ag \(', 'ag -Q \(')])
def test_get_new_command(script, new_cmd, stderr):
assert get_new_command((Command(script=script, stderr=stderr))) == new_cmd

View File

@@ -7,6 +7,8 @@ from tests.utils import Command
(Command(script='vim', stderr='vim: command not found'), (Command(script='vim', stderr='vim: command not found'),
[('vim', 'main'), ('vim-tiny', 'main')]), [('vim', 'main'), ('vim-tiny', 'main')]),
(Command(script='sudo vim', stderr='vim: command not found'), (Command(script='sudo vim', stderr='vim: command not found'),
[('vim', 'main'), ('vim-tiny', 'main')]),
(Command(script='vim', stderr="The program 'vim' is currently not installed. You can install it by typing: sudo apt install vim"),
[('vim', 'main'), ('vim-tiny', 'main')])]) [('vim', 'main'), ('vim-tiny', 'main')])])
def test_match(mocker, command, packages): def test_match(mocker, command, packages):
mocker.patch('thefuck.rules.apt_get.which', return_value=None) mocker.patch('thefuck.rules.apt_get.which', return_value=None)

101
tests/rules/test_aws_cli.py Normal file
View File

@@ -0,0 +1,101 @@
import pytest
from thefuck.rules.aws_cli import match, get_new_command
from tests.utils import Command
no_suggestions = '''\
usage: aws [options] <command> <subcommand> [<subcommand> ...] [parameters]
To see help text, you can run:
aws help
aws <command> help
aws <command> <subcommand> help
aws: error: argument command: Invalid choice, valid choices are:
dynamodb | dynamodbstreams
ec2 | ecr
'''
misspelled_command = '''\
usage: aws [options] <command> <subcommand> [<subcommand> ...] [parameters]
To see help text, you can run:
aws help
aws <command> help
aws <command> <subcommand> help
aws: error: argument command: Invalid choice, valid choices are:
dynamodb | dynamodbstreams
ec2 | ecr
Invalid choice: 'dynamdb', maybe you meant:
* dynamodb
'''
misspelled_subcommand = '''\
usage: aws [options] <command> <subcommand> [<subcommand> ...] [parameters]
To see help text, you can run:
aws help
aws <command> help
aws <command> <subcommand> help
aws: error: argument operation: Invalid choice, valid choices are:
query | scan
update-item | update-table
Invalid choice: 'scn', maybe you meant:
* scan
'''
misspelled_subcommand_with_multiple_options = '''\
usage: aws [options] <command> <subcommand> [<subcommand> ...] [parameters]
To see help text, you can run:
aws help
aws <command> help
aws <command> <subcommand> help
aws: error: argument operation: Invalid choice, valid choices are:
describe-table | get-item
list-tables | put-item
Invalid choice: 't-item', maybe you meant:
* put-item
* get-item
'''
@pytest.mark.parametrize('command', [
Command('aws dynamdb scan', stderr=misspelled_command),
Command('aws dynamodb scn', stderr=misspelled_subcommand),
Command('aws dynamodb t-item',
stderr=misspelled_subcommand_with_multiple_options)])
def test_match(command):
assert match(command)
def test_not_match():
assert not match(Command('aws dynamodb invalid', stderr=no_suggestions))
@pytest.mark.parametrize('command, result', [
(Command('aws dynamdb scan', stderr=misspelled_command),
['aws dynamodb scan']),
(Command('aws dynamodb scn', stderr=misspelled_subcommand),
['aws dynamodb scan']),
(Command('aws dynamodb t-item',
stderr=misspelled_subcommand_with_multiple_options),
['aws dynamodb put-item', 'aws dynamodb get-item'])])
def test_get_new_command(command, result):
assert get_new_command(command) == result

View File

@@ -0,0 +1,38 @@
import pytest
from tests.utils import Command
from thefuck.rules.brew_link import get_new_command, match
@pytest.fixture
def stderr():
return ("Error: Could not symlink bin/gcp\n"
"Target /usr/local/bin/gcp\n"
"already exists. You may want to remove it:\n"
" rm '/usr/local/bin/gcp'\n"
"\n"
"To force the link and overwrite all conflicting files:\n"
" brew link --overwrite coreutils\n"
"\n"
"To list all files that would be deleted:\n"
" brew link --overwrite --dry-run coreutils\n")
@pytest.fixture
def new_command(formula):
return 'brew link --overwrite --dry-run {}'.format(formula)
@pytest.mark.parametrize('script', ['brew link coreutils', 'brew ln coreutils'])
def test_match(stderr, script):
assert match(Command(script=script, stderr=stderr))
@pytest.mark.parametrize('script', ['brew link coreutils'])
def test_not_match(script):
stderr = ''
assert not match(Command(script=script, stderr=stderr))
@pytest.mark.parametrize('script, formula, ', [('brew link coreutils', 'coreutils')])
def test_get_new_command(stderr, new_command, script, formula):
assert get_new_command(Command(script=script, stderr=stderr)) == new_command

View File

@@ -0,0 +1,31 @@
import pytest
from tests.utils import Command
from thefuck.rules.brew_uninstall import get_new_command, match
@pytest.fixture
def stdout():
return ("Uninstalling /usr/local/Cellar/tbb/4.4-20160916... (118 files, 1.9M)\n"
"tbb 4.4-20160526, 4.4-20160722 are still installed.\n"
"Remove all versions with `brew uninstall --force tbb`.\n")
@pytest.fixture
def new_command(formula):
return 'brew uninstall --force {}'.format(formula)
@pytest.mark.parametrize('script', ['brew uninstall tbb', 'brew rm tbb', 'brew remove tbb'])
def test_match(stdout, script):
assert match(Command(script=script, stdout=stdout))
@pytest.mark.parametrize('script', ['brew remove gnuplot'])
def test_not_match(script):
stdout = 'Uninstalling /usr/local/Cellar/gnuplot/5.0.4_1... (44 files, 2.3M)\n'
assert not match(Command(script=script, stdout=stdout))
@pytest.mark.parametrize('script, formula, ', [('brew uninstall tbb', 'tbb')])
def test_get_new_command(stdout, new_command, script, formula):
assert get_new_command(Command(script=script, stdout=stdout)) == new_command

View File

@@ -21,8 +21,8 @@ def test_match(brew_unknown_cmd):
def test_get_new_command(brew_unknown_cmd, brew_unknown_cmd2): def test_get_new_command(brew_unknown_cmd, brew_unknown_cmd2):
assert get_new_command(Command('brew inst', stderr=brew_unknown_cmd)) \ assert (get_new_command(Command('brew inst', stderr=brew_unknown_cmd))
== ['brew list', 'brew install', 'brew uninstall'] == ['brew list', 'brew install', 'brew uninstall'])
cmds = get_new_command(Command('brew instaa', stderr=brew_unknown_cmd2)) cmds = get_new_command(Command('brew instaa', stderr=brew_unknown_cmd2))
assert 'brew install' in cmds assert 'brew install' in cmds

View File

@@ -48,9 +48,9 @@ def test_match(composer_not_command, composer_not_command_one_of_this):
def test_get_new_command(composer_not_command, composer_not_command_one_of_this): def test_get_new_command(composer_not_command, composer_not_command_one_of_this):
assert get_new_command(Command('composer udpate', assert (get_new_command(Command('composer udpate',
stderr=composer_not_command)) \ stderr=composer_not_command))
== 'composer update' == 'composer update')
assert get_new_command( assert (get_new_command(Command('composer pdate',
Command('composer pdate', stderr=composer_not_command_one_of_this)) \ stderr=composer_not_command_one_of_this))
== 'composer selfupdate' == 'composer selfupdate')

View File

@@ -2,7 +2,7 @@ import os
import pytest import pytest
import tarfile import tarfile
from thefuck.rules.dirty_untar import match, get_new_command, side_effect, \ from thefuck.rules.dirty_untar import match, get_new_command, side_effect, \
tar_extensions tar_extensions # noqa: E126
from tests.utils import Command from tests.utils import Command
@@ -33,12 +33,12 @@ def tar_error(tmpdir):
return fixture return fixture
parametrize_extensions = pytest.mark.parametrize('ext', tar_extensions) parametrize_extensions = pytest.mark.parametrize('ext', tar_extensions)
# (filename as typed by the user, unquoted filename, quoted filename as per shells.quote) # (filename as typed by the user, unquoted filename, quoted filename as per shells.quote)
parametrize_filename = pytest.mark.parametrize('filename, unquoted, quoted', [ parametrize_filename = pytest.mark.parametrize('filename, unquoted, quoted', [
('foo{}', 'foo{}', 'foo{}'), ('foo{}', 'foo{}', 'foo{}'),
('foo\ bar{}', 'foo bar{}', "'foo bar{}'"),
('"foo bar{}"', 'foo bar{}', "'foo bar{}'")]) ('"foo bar{}"', 'foo bar{}', "'foo bar{}'")])
parametrize_script = pytest.mark.parametrize('script, fixed', [ parametrize_script = pytest.mark.parametrize('script, fixed', [

View File

@@ -64,7 +64,6 @@ def test_side_effect(zip_error, script, filename):
@pytest.mark.parametrize('script,fixed,filename', [ @pytest.mark.parametrize('script,fixed,filename', [
(u'unzip café', u"unzip café -d 'café'", u'café.zip'), (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', 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 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')]) (u'unzip foo.zip', u'unzip foo.zip -d foo', u'foo.zip')])
def test_get_new_command(zip_error, script, fixed, filename): def test_get_new_command(zip_error, script, fixed, filename):

View File

@@ -37,7 +37,7 @@ south.exceptions.GhostMigrations:
! I'm not trusting myself; either fix this yourself by fiddling ! I'm not trusting myself; either fix this yourself by fiddling
! with the south_migrationhistory table, or pass --delete-ghost-migrations ! with the south_migrationhistory table, or pass --delete-ghost-migrations
! to South to have it delete ALL of these records (this may not be good). ! to South to have it delete ALL of these records (this may not be good).
''' ''' # noqa
def test_match(stderr): def test_match(stderr):

View File

@@ -39,5 +39,5 @@ def test_match(stderr):
def test_get_new_command(): def test_get_new_command():
assert get_new_command(Command('./manage.py migrate auth')) \ assert (get_new_command(Command('./manage.py migrate auth'))
== './manage.py migrate auth --merge' == './manage.py migrate auth --merge')

View File

@@ -45,5 +45,5 @@ def test_not_match(command):
'fab prepare_extension:version=2016 deploy:beta=true -H the.fuck'), 'fab prepare_extension:version=2016 deploy:beta=true -H the.fuck'),
]) ])
def test_get_new_command(script, result): def test_get_new_command(script, result):
command = Command(script, stdout,stderr) command = Command(script, stdout, stderr)
assert get_new_command(command) == result assert get_new_command(command) == result

View File

@@ -18,5 +18,5 @@ def test_match():
def test_get_new_command(): def test_get_new_command():
""" Replace the Alt+Space character by a simple space """ """ Replace the Alt+Space character by a simple space """
assert get_new_command(Command(u'ps -ef | grep foo'))\ assert (get_new_command(Command(u'ps -ef | grep foo'))
== 'ps -ef | grep foo' == 'ps -ef | grep foo')

View File

@@ -191,7 +191,7 @@ E NameError: name 'mocker' is not defined
/home/thefuck/tests/rules/test_fix_file.py:218: NameError /home/thefuck/tests/rules/test_fix_file.py:218: NameError
""", ''), """, ''),
) ) # noqa
@pytest.mark.parametrize('test', tests) @pytest.mark.parametrize('test', tests)
@@ -227,10 +227,6 @@ def test_get_new_command(mocker, monkeypatch, test):
mocker.patch('os.path.isfile', return_value=True) mocker.patch('os.path.isfile', return_value=True)
monkeypatch.setenv('EDITOR', 'dummy_editor') monkeypatch.setenv('EDITOR', 'dummy_editor')
cmd = Command(script=test[0], stdout=test[4], stderr=test[5])
#assert (get_new_command(cmd, Settings({})) ==
# 'dummy_editor {} +{} && {}'.format(test[1], test[2], test[0]))
@pytest.mark.parametrize('test', tests) @pytest.mark.parametrize('test', tests)
@pytest.mark.usefixtures('no_memoize') @pytest.mark.usefixtures('no_memoize')
@@ -243,7 +239,7 @@ def test_get_new_command_with_settings(mocker, monkeypatch, test, settings):
if test[3]: if test[3]:
assert (get_new_command(cmd) == assert (get_new_command(cmd) ==
u'dummy_editor {} +{}:{} && {}'.format(test[1], test[2], test[3], test[0])) u'dummy_editor {} +{}:{} && {}'.format(test[1], test[2], test[3], test[0]))
else: else:
assert (get_new_command(cmd) == assert (get_new_command(cmd) ==
u'dummy_editor {} +{} && {}'.format(test[1], test[2], test[0])) u'dummy_editor {} +{} && {}'.format(test[1], test[2], test[0]))

View File

@@ -0,0 +1,82 @@
import pytest
from six import BytesIO
from thefuck.rules.gem_unknown_command import match, get_new_command
from tests.utils import Command
stderr = '''
ERROR: While executing gem ... (Gem::CommandLineError)
Unknown command {}
'''
gem_help_commands_stdout = b'''
GEM commands are:
build Build a gem from a gemspec
cert Manage RubyGems certificates and signing settings
check Check a gem repository for added or missing files
cleanup Clean up old versions of installed gems
contents Display the contents of the installed gems
dependency Show the dependencies of an installed gem
environment Display information about the RubyGems environment
fetch Download a gem and place it in the current directory
generate_index Generates the index files for a gem server directory
help Provide help on the 'gem' command
install Install a gem into the local repository
list Display local gems whose name matches REGEXP
lock Generate a lockdown list of gems
mirror Mirror all gem files (requires rubygems-mirror)
open Open gem sources in editor
outdated Display all gems that need updates
owner Manage gem owners of a gem on the push server
pristine Restores installed gems to pristine condition from files
located in the gem cache
push Push a gem up to the gem server
query Query gem information in local or remote repositories
rdoc Generates RDoc for pre-installed gems
search Display remote gems whose name matches REGEXP
server Documentation and gem repository HTTP server
sources Manage the sources and cache file RubyGems uses to search
for gems
specification Display gem specification (in yaml)
stale List gems along with access times
uninstall Uninstall gems from the local repository
unpack Unpack an installed gem to the current directory
update Update installed gems to the latest version
which Find the location of a library file you can require
yank Remove a pushed gem from the index
For help on a particular command, use 'gem help COMMAND'.
Commands may be abbreviated, so long as they are unambiguous.
e.g. 'gem i rake' is short for 'gem install rake'.
'''
@pytest.fixture(autouse=True)
def gem_help_commands(mocker):
patch = mocker.patch('subprocess.Popen')
patch.return_value.stdout = BytesIO(gem_help_commands_stdout)
return patch
@pytest.mark.parametrize('script, command', [
('gem isntall jekyll', 'isntall'),
('gem last --local', 'last')])
def test_match(script, command):
assert match(Command(script, stderr=stderr.format(command)))
@pytest.mark.parametrize('script, stderr', [
('gem install jekyll', ''),
('git log', stderr.format('log'))])
def test_not_match(script, stderr):
assert not match(Command(script, stderr=stderr))
@pytest.mark.parametrize('script, stderr, result', [
('gem isntall jekyll', stderr.format('isntall'), 'gem install jekyll'),
('gem last --local', stderr.format('last'), 'gem list --local')])
def test_get_new_command(script, stderr, result):
new_command = get_new_command(Command(script, stderr=stderr))
assert new_command[0] == result

View File

@@ -3,6 +3,12 @@ from thefuck.rules.git_add import match, get_new_command
from tests.utils import Command from tests.utils import Command
@pytest.fixture(autouse=True)
def path_exists(mocker):
return mocker.patch('thefuck.rules.git_add.Path.exists',
return_value=True)
@pytest.fixture @pytest.fixture
def stderr(target): def stderr(target):
return ("error: pathspec '{}' did not match any " return ("error: pathspec '{}' did not match any "
@@ -16,10 +22,13 @@ def test_match(stderr, script, target):
assert match(Command(script=script, stderr=stderr)) assert match(Command(script=script, stderr=stderr))
@pytest.mark.parametrize('script', [ @pytest.mark.parametrize('script, target, exists', [
'git submodule update known', 'git commit known']) ('git submodule update known', '', True),
def test_not_match(script): ('git commit known', '', True),
assert not match(Command(script=script, stderr='')) ('git submodule update known', stderr, False)])
def test_not_match(path_exists, stderr, script, target, exists):
path_exists.return_value = exists
assert not match(Command(script=script, stderr=stderr))
@pytest.mark.parametrize('script, target, new_command', [ @pytest.mark.parametrize('script, target, new_command', [

View File

@@ -0,0 +1,22 @@
import pytest
from thefuck.rules.git_add_force import match, get_new_command
from tests.utils import Command
@pytest.fixture
def stderr():
return ('The following paths are ignored by one of your .gitignore files:\n'
'dist/app.js\n'
'dist/background.js\n'
'dist/options.js\n'
'Use -f if you really want to add them.\n')
def test_match(stderr):
assert match(Command('git add dist/*.js', stderr=stderr))
assert not match(Command('git add dist/*.js'))
def test_get_new_command(stderr):
assert (get_new_command(Command('git add dist/*.js', stderr=stderr))
== "git add --force dist/*.js")

View File

@@ -0,0 +1,30 @@
import pytest
from tests.utils import Command
from thefuck.rules.git_bisect_usage import match, get_new_command
@pytest.fixture
def stderr():
return ("usage: git bisect [help|start|bad|good|new|old"
"|terms|skip|next|reset|visualize|replay|log|run]")
@pytest.mark.parametrize('script', [
'git bisect strt', 'git bisect rset', 'git bisect goood'])
def test_match(stderr, script):
assert match(Command(script=script, stderr=stderr))
@pytest.mark.parametrize('script', [
'git bisect', 'git bisect start', 'git bisect good'])
def test_not_match(script):
assert not match(Command(script=script, stderr=''))
@pytest.mark.parametrize('script, new_cmd, ', [
('git bisect goood', ['good', 'old', 'log']),
('git bisect strt', ['start', 'terms', 'reset']),
('git bisect rset', ['reset', 'next', 'start'])])
def test_get_new_command(stderr, script, new_cmd):
new_cmd = ['git bisect %s' % cmd for cmd in new_cmd]
assert get_new_command(Command(script=script, stderr=stderr)) == new_cmd

View File

@@ -0,0 +1,23 @@
import pytest
from thefuck.rules.git_diff_no_index import match, get_new_command
from tests.utils import Command
@pytest.mark.parametrize('command', [
Command(script='git diff foo bar')])
def test_match(command):
assert match(command)
@pytest.mark.parametrize('command', [
Command(script='git diff --no-index foo bar'),
Command(script='git diff foo'),
Command(script='git diff foo bar baz')])
def test_not_match(command):
assert not match(command)
@pytest.mark.parametrize('command, new_command', [
(Command('git diff foo bar'), 'git diff --no-index foo bar')])
def test_get_new_command(command, new_command):
assert get_new_command(command) == new_command

View File

@@ -0,0 +1,31 @@
import pytest
from thefuck.rules.git_flag_after_filename import match, get_new_command
from tests.utils import Command
command1 = Command('git log README.md -p',
stderr="fatal: bad flag '-p' used after filename")
command2 = Command('git log README.md -p CONTRIBUTING.md',
stderr="fatal: bad flag '-p' used after filename")
command3 = Command('git log -p README.md --name-only',
stderr="fatal: bad flag '--name-only' used after filename")
@pytest.mark.parametrize('command', [
command1, command2, command3])
def test_match(command):
assert match(command)
@pytest.mark.parametrize('command', [
Command('git log README.md'),
Command('git log -p README.md')])
def test_not_match(command):
assert not match(command)
@pytest.mark.parametrize('command, result', [
(command1, "git log -p README.md"),
(command2, "git log -p README.md CONTRIBUTING.md"),
(command3, "git log -p --name-only README.md")])
def test_get_new_command(command, result):
assert get_new_command(command) == result

View File

@@ -7,7 +7,7 @@ from tests.utils import Command
def git_not_command(): def git_not_command():
return """git: 'brnch' is not a git command. See 'git --help'. return """git: 'brnch' is not a git command. See 'git --help'.
Did you mean this? The most similar command is
branch branch
""" """
@@ -16,7 +16,7 @@ branch
def git_not_command_one_of_this(): def git_not_command_one_of_this():
return """git: 'st' is not a git command. See 'git --help'. return """git: 'st' is not a git command. See 'git --help'.
Did you mean one of these? The most similar commands are
status status
reset reset
stage stage
@@ -29,7 +29,7 @@ stats
def git_not_command_closest(): def git_not_command_closest():
return '''git: 'tags' is not a git command. See 'git --help'. return '''git: 'tags' is not a git command. See 'git --help'.
Did you mean one of these? The most similar commands are
\tstage \tstage
\ttag \ttag
''' '''
@@ -49,9 +49,9 @@ def test_match(git_not_command, git_command, git_not_command_one_of_this):
def test_get_new_command(git_not_command, git_not_command_one_of_this, def test_get_new_command(git_not_command, git_not_command_one_of_this,
git_not_command_closest): git_not_command_closest):
assert get_new_command(Command('git brnch', stderr=git_not_command)) \ assert (get_new_command(Command('git brnch', stderr=git_not_command))
== ['git branch'] == ['git branch'])
assert get_new_command(Command('git st', stderr=git_not_command_one_of_this)) \ assert (get_new_command(Command('git st', stderr=git_not_command_one_of_this))
== ['git stats', 'git stash', 'git stage'] == ['git stats', 'git stash', 'git stage'])
assert get_new_command(Command('git tags', stderr=git_not_command_closest)) \ assert (get_new_command(Command('git tags', stderr=git_not_command_closest))
== ['git tag', 'git stage'] == ['git tag', 'git stage'])

View File

@@ -25,5 +25,5 @@ def test_match(stderr):
def test_get_new_command(stderr): def test_get_new_command(stderr):
assert get_new_command(Command('git pull', stderr=stderr)) \ assert (get_new_command(Command('git pull', stderr=stderr))
== "git branch --set-upstream-to=origin/master master && git pull" == "git branch --set-upstream-to=origin/master master && git pull")

View File

@@ -0,0 +1,19 @@
import pytest
from thefuck.rules.git_pull_uncommitted_changes import match, get_new_command
from tests.utils import Command
@pytest.fixture
def stderr():
return '''error: Cannot pull with rebase: You have unstaged changes.'''
def test_match(stderr):
assert match(Command('git pull', stderr=stderr))
assert not match(Command('git pull'))
assert not match(Command('ls', stderr=stderr))
def test_get_new_command(stderr):
assert (get_new_command(Command('git pull', stderr=stderr))
== "git stash && git pull && git stash pop")

View File

@@ -0,0 +1,19 @@
import pytest
from thefuck.rules.git_pull_uncommitted_changes import match, get_new_command
from tests.utils import Command
@pytest.fixture
def stderr():
return '''error: Cannot pull with rebase: Your index contains uncommitted changes.'''
def test_match(stderr):
assert match(Command('git pull', stderr=stderr))
assert not match(Command('git pull'))
assert not match(Command('ls', stderr=stderr))
def test_get_new_command(stderr):
assert (get_new_command(Command('git pull', stderr=stderr))
== "git stash && git pull && git stash pop")

View File

@@ -14,6 +14,7 @@ To push the current branch and set the remote as upstream, use
def test_match(stderr): def test_match(stderr):
assert match(Command('git push', stderr=stderr))
assert match(Command('git push master', stderr=stderr)) assert match(Command('git push master', stderr=stderr))
assert not match(Command('git push master')) assert not match(Command('git push master'))
assert not match(Command('ls', stderr=stderr)) assert not match(Command('ls', stderr=stderr))
@@ -22,3 +23,11 @@ def test_match(stderr):
def test_get_new_command(stderr): def test_get_new_command(stderr):
assert get_new_command(Command('git push', stderr=stderr))\ assert get_new_command(Command('git push', stderr=stderr))\
== "git push --set-upstream origin master" == "git push --set-upstream origin master"
assert get_new_command(Command('git push -u', stderr=stderr))\
== "git push --set-upstream origin master"
assert get_new_command(Command('git push -u origin', stderr=stderr))\
== "git push --set-upstream origin master"
assert get_new_command(Command('git push --set-upstream origin', stderr=stderr))\
== "git push --set-upstream origin master"
assert get_new_command(Command('git push --quiet', stderr=stderr))\
== "git push --set-upstream origin master --quiet"

View File

@@ -39,12 +39,7 @@ To /tmp/bar
@pytest.mark.parametrize('command', [ @pytest.mark.parametrize('command', [
Command(script='git push', stderr=git_err), Command(script='git push', stderr=git_err),
Command(script='git push nvbn', stderr=git_err), Command(script='git push nvbn', stderr=git_err),
Command(script='git push nvbn master', stderr=git_err)]) Command(script='git push nvbn master', stderr=git_err),
def test_match(command):
assert match(command)
@pytest.mark.parametrize('command', [
Command(script='git push', stderr=git_err2), Command(script='git push', stderr=git_err2),
Command(script='git push nvbn', stderr=git_err2), Command(script='git push nvbn', stderr=git_err2),
Command(script='git push nvbn master', stderr=git_err2)]) Command(script='git push nvbn master', stderr=git_err2)])
@@ -68,12 +63,7 @@ def test_not_match(command):
(Command(script='git push nvbn', stderr=git_err), (Command(script='git push nvbn', stderr=git_err),
'git pull nvbn && git push nvbn'), 'git pull nvbn && git push nvbn'),
(Command(script='git push nvbn master', stderr=git_err), (Command(script='git push nvbn master', stderr=git_err),
'git pull nvbn master && git push nvbn master')]) 'git pull nvbn master && git push nvbn master'),
def test_get_new_command(command, output):
assert get_new_command(command) == output
@pytest.mark.parametrize('command, output', [
(Command(script='git push', stderr=git_err2), 'git pull && git push'), (Command(script='git push', stderr=git_err2), 'git pull && git push'),
(Command(script='git push nvbn', stderr=git_err2), (Command(script='git push nvbn', stderr=git_err2),
'git pull nvbn && git push nvbn'), 'git pull nvbn && git push nvbn'),

View 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

View File

@@ -0,0 +1,40 @@
import pytest
from thefuck.rules.git_rebase_merge_dir import match, get_new_command
from tests.utils import Command
@pytest.fixture
def stderr():
return ('\n\nIt seems that there is already a rebase-merge directory, and\n'
'I wonder if you are in the middle of another rebase. If that is the\n'
'case, please try\n'
'\tgit rebase (--continue | --abort | --skip)\n'
'If that is not the case, please\n'
'\trm -fr "/foo/bar/baz/egg/.git/rebase-merge"\n'
'and run me again. I am stopping in case you still have something\n'
'valuable there.\n')
@pytest.mark.parametrize('script', [
('git rebase master'), ('git rebase -skip'), ('git rebase')])
def test_match(stderr, script):
assert match(Command(script=script, stderr=stderr))
@pytest.mark.parametrize('script', ['git rebase master', 'git rebase -abort'])
def test_not_match(script):
assert not match(Command(script=script))
@pytest.mark.parametrize('script, result', [
('git rebase master', [
'git rebase --abort', 'git rebase --skip', 'git rebase --continue',
'rm -fr "/foo/bar/baz/egg/.git/rebase-merge"']),
('git rebase -skip', [
'git rebase --skip', 'git rebase --abort', 'git rebase --continue',
'rm -fr "/foo/bar/baz/egg/.git/rebase-merge"']),
('git rebase', [
'git rebase --skip', 'git rebase --abort', 'git rebase --continue',
'rm -fr "/foo/bar/baz/egg/.git/rebase-merge"'])])
def test_get_new_command(stderr, script, result):
assert get_new_command(Command(script=script, stderr=stderr)) == result

View File

@@ -14,11 +14,11 @@ def test_match(command):
Command('git remote add origin url'), Command('git remote add origin url'),
Command('git remote remove origin'), Command('git remote remove origin'),
Command('git remote prune origin'), Command('git remote prune origin'),
Command('git remote set-branches origin branch') Command('git remote set-branches origin branch')])
])
def test_not_match(command): def test_not_match(command):
assert not match(command) assert not match(command)
@pytest.mark.parametrize('command, new_command', [ @pytest.mark.parametrize('command, new_command', [
(Command('git remote set-url origin git@github.com:nvbn/thefuck.git'), (Command('git remote set-url origin git@github.com:nvbn/thefuck.git'),
'git remote add origin git@github.com:nvbn/thefuck.git')]) 'git remote add origin git@github.com:nvbn/thefuck.git')])

View File

@@ -0,0 +1,28 @@
import pytest
from thefuck.rules.git_rm_local_modifications import match, get_new_command
from tests.utils import Command
@pytest.fixture
def stderr(target):
return ('error: the following file has local modifications:\n {}\n(use '
'--cached to keep the file, or -f to force removal)').format(target)
@pytest.mark.parametrize('script, target', [
('git rm foo', 'foo'),
('git rm foo bar', 'bar')])
def test_match(stderr, script, target):
assert match(Command(script=script, stderr=stderr))
@pytest.mark.parametrize('script', ['git rm foo', 'git rm foo bar', 'git rm'])
def test_not_match(script):
assert not match(Command(script=script, stderr=''))
@pytest.mark.parametrize('script, target, new_command', [
('git rm foo', 'foo', ['git rm --cached foo', 'git rm -f foo']),
('git rm foo bar', 'bar', ['git rm --cached foo bar', 'git rm -f foo bar'])])
def test_get_new_command(stderr, script, target, new_command):
assert get_new_command(Command(script=script, stderr=stderr)) == new_command

View File

@@ -0,0 +1,28 @@
import pytest
from thefuck.rules.git_rm_staged import match, get_new_command
from tests.utils import Command
@pytest.fixture
def stderr(target):
return ('error: the following file has changes staged in the index:\n {}\n(use '
'--cached to keep the file, or -f to force removal)').format(target)
@pytest.mark.parametrize('script, target', [
('git rm foo', 'foo'),
('git rm foo bar', 'bar')])
def test_match(stderr, script, target):
assert match(Command(script=script, stderr=stderr))
@pytest.mark.parametrize('script', ['git rm foo', 'git rm foo bar', 'git rm'])
def test_not_match(script):
assert not match(Command(script=script, stderr=''))
@pytest.mark.parametrize('script, target, new_command', [
('git rm foo', 'foo', ['git rm --cached foo', 'git rm -f foo']),
('git rm foo bar', 'bar', ['git rm --cached foo bar', 'git rm -f foo bar'])])
def test_get_new_command(stderr, script, target, new_command):
assert get_new_command(Command(script=script, stderr=stderr)) == new_command

View File

@@ -4,14 +4,14 @@ from tests.utils import Command
cherry_pick_error = ( cherry_pick_error = (
'error: Your local changes would be overwritten by cherry-pick.\n' 'error: Your local changes would be overwritten by cherry-pick.\n'
'hint: Commit your changes or stash them to proceed.\n' 'hint: Commit your changes or stash them to proceed.\n'
'fatal: cherry-pick failed') 'fatal: cherry-pick failed')
rebase_error = ( rebase_error = (
'Cannot rebase: Your index contains uncommitted changes.\n' 'Cannot rebase: Your index contains uncommitted changes.\n'
'Please commit or stash them.') 'Please commit or stash them.')
@pytest.mark.parametrize('command', [ @pytest.mark.parametrize('command', [

View File

@@ -0,0 +1,18 @@
import pytest
from thefuck.rules.git_stash_pop import match, get_new_command
from tests.utils import Command
@pytest.fixture
def stderr():
return '''error: Your local changes to the following files would be overwritten by merge:'''
def test_match(stderr):
assert match(Command('git stash pop', stderr=stderr))
assert not match(Command('git stash'))
def test_get_new_command(stderr):
assert (get_new_command(Command('git stash pop', stderr=stderr))
== "git add --update && git stash pop && git reset .")

View File

@@ -0,0 +1,18 @@
import pytest
from thefuck.rules.git_tag_force import match, get_new_command
from tests.utils import Command
@pytest.fixture
def stderr():
return '''fatal: tag 'alert' already exists'''
def test_match(stderr):
assert match(Command('git tag alert', stderr=stderr))
assert not match(Command('git tag alert'))
def test_get_new_command(stderr):
assert (get_new_command(Command('git tag alert', stderr=stderr))
== "git tag --force alert")

View File

@@ -7,7 +7,7 @@ def test_match():
with patch('os.path.exists', return_value=True): with patch('os.path.exists', return_value=True):
assert match(Command(script='main', stderr='main: command not found')) assert match(Command(script='main', stderr='main: command not found'))
assert match(Command(script='main --help', assert match(Command(script='main --help',
stderr='main: command not found')) stderr='main: command not found'))
assert not match(Command(script='main', stderr='')) assert not match(Command(script='main', stderr=''))
with patch('os.path.exists', return_value=False): with patch('os.path.exists', return_value=False):

View File

@@ -1,34 +1,31 @@
# -*- coding: utf-8 -*-
import pytest import pytest
from tests.utils import Command from tests.utils import Command
from thefuck.rules.heroku_not_command import match, get_new_command from thefuck.rules.heroku_not_command import match, get_new_command
def suggest_stderr(cmd): suggest_stderr = '''
return ''' ! `{}` is not a heroku command. log is not a heroku command.
! Perhaps you meant `logs`, `pg`. Perhaps you meant logs?
! See `heroku help` for a list of available commands.'''.format(cmd) ▸ Run heroku _ to run heroku logs.
▸ Run heroku help for a list of available commands.'''
no_suggest_stderr = ''' ! `aaaaa` is not a heroku command. @pytest.mark.parametrize('cmd', ['log'])
! See `heroku help` for a list of available commands.'''
@pytest.mark.parametrize('cmd', ['log', 'pge'])
def test_match(cmd): def test_match(cmd):
assert match( assert match(
Command('heroku {}'.format(cmd), stderr=suggest_stderr(cmd))) Command('heroku {}'.format(cmd), stderr=suggest_stderr))
@pytest.mark.parametrize('script, stderr', [ @pytest.mark.parametrize('script, stderr', [
('cat log', suggest_stderr('log')), ('cat log', suggest_stderr)])
('heroku aaa', no_suggest_stderr)])
def test_not_match(script, stderr): def test_not_match(script, stderr):
assert not match(Command(script, stderr=stderr)) assert not match(Command(script, stderr=stderr))
@pytest.mark.parametrize('cmd, result', [ @pytest.mark.parametrize('cmd, result', [
('log', ['heroku logs', 'heroku pg']), ('log', 'heroku logs')])
('pge', ['heroku pg', 'heroku logs'])])
def test_get_new_command(cmd, result): 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 assert get_new_command(command) == result

View File

@@ -0,0 +1,29 @@
import pytest
from thefuck.rules.hostscli import no_website, get_new_command, match
from tests.utils import Command
no_website_long = '''
{}:
No Domain list found for website: a_website_that_does_not_exist
Please raise a Issue here: https://github.com/dhilipsiva/hostscli/issues/new
if you think we should add domains for this website.
type `hostscli websites` to see a list of websites that you can block/unblock
'''.format(no_website)
@pytest.mark.parametrize('command', [
Command('hostscli block a_website_that_does_not_exist',
stderr=no_website_long)])
def test_match(command):
assert match(command)
@pytest.mark.parametrize('command, result', [(
Command('hostscli block a_website_that_does_not_exist',
stderr=no_website_long),
['hostscli websites'])])
def test_get_new_command(command, result):
assert get_new_command(command) == result

View File

@@ -0,0 +1,53 @@
import pytest
from six import BytesIO
from thefuck.rules.ifconfig_device_not_found import match, get_new_command
from tests.utils import Command
stderr = '{}: error fetching interface information: Device not found'
stdout = b'''
wlp2s0 Link encap:Ethernet HWaddr 5c:51:4f:7c:58:5d
inet addr:192.168.0.103 Bcast:192.168.0.255 Mask:255.255.255.0
inet6 addr: fe80::be23:69b9:96d2:6d39/64 Scope:Link
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:23581604 errors:0 dropped:0 overruns:0 frame:0
TX packets:17017655 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:16148429061 (16.1 GB) TX bytes:7067533695 (7.0 GB)
'''
@pytest.fixture(autouse=True)
def ifconfig(mocker):
mock = mocker.patch(
'thefuck.rules.ifconfig_device_not_found.subprocess.Popen')
mock.return_value.stdout = BytesIO(stdout)
return mock
@pytest.mark.parametrize('script, stderr', [
('ifconfig wlan0', stderr.format('wlan0')),
('ifconfig -s eth0', stderr.format('eth0')),
])
def test_match(script, stderr):
assert match(Command(script, stderr=stderr))
@pytest.mark.parametrize('script, stderr', [
('config wlan0',
'wlan0: error fetching interface information: Device not found'),
('ifconfig eth0', ''),
])
def test_not_match(script, stderr):
assert not match(Command(script, stderr=stderr))
@pytest.mark.parametrize('script, result', [
('ifconfig wlan0', ['ifconfig wlp2s0']),
('ifconfig -s wlan0', ['ifconfig -s wlp2s0']),
])
def test_get_new_comman(script, result):
new_command = get_new_command(
Command(script, stderr=stderr.format('wlan0')))
assert new_command == result

View File

@@ -19,5 +19,5 @@ def test_match(is_not_task):
def test_get_new_command(is_not_task): def test_get_new_command(is_not_task):
assert get_new_command(Command(script='lein rpl --help', stderr=is_not_task)) \ assert (get_new_command(Command(script='lein rpl --help', stderr=is_not_task))
== ['lein repl --help', 'lein jar --help'] == ['lein repl --help', 'lein jar --help'])

View File

@@ -11,16 +11,6 @@ def file_exists(mocker):
get_stderr = "ln: failed to create symbolic link '{}': File exists".format get_stderr = "ln: failed to create symbolic link '{}': File exists".format
@pytest.mark.usefixtures('file_exists')
@pytest.mark.parametrize('script', [
'ln -s dest source',
'ln dest -s source',
'ln dest source -s'])
def test_match(script):
stderr = get_stderr('source')
assert match(Command(script, stderr=stderr))
@pytest.mark.parametrize('script, stderr, exists', [ @pytest.mark.parametrize('script, stderr, exists', [
('ln dest source', get_stderr('source'), True), ('ln dest source', get_stderr('source'), True),
('ls -s dest source', get_stderr('source'), True), ('ls -s dest source', get_stderr('source'), True),
@@ -38,4 +28,5 @@ def test_not_match(file_exists, script, stderr, exists):
('ln dest source -s', 'ln source -s dest')]) ('ln dest source -s', 'ln source -s dest')])
def test_match(script, result): def test_match(script, result):
stderr = get_stderr('source') stderr = get_stderr('source')
assert match(Command(script, stderr=stderr))
assert get_new_command(Command(script, stderr=stderr)) == result assert get_new_command(Command(script, stderr=stderr)) == result

View File

@@ -0,0 +1,12 @@
from thefuck.rules.ls_all import match, get_new_command
from tests.utils import Command
def test_match():
assert match(Command(script='ls'))
assert not match(Command(script='ls', stdout='file.py\n'))
def test_get_new_command():
assert get_new_command(Command(script='ls empty_dir')) == 'ls -A empty_dir'
assert get_new_command(Command(script='ls')) == 'ls -A'

View File

@@ -23,7 +23,8 @@ def test_not_match(command):
@pytest.mark.parametrize('command, new_command', [ @pytest.mark.parametrize('command, new_command', [
(Command('man read'), ['man 3 read', 'man 2 read']), (Command('man read'), ['man 3 read', 'man 2 read', 'read --help']),
(Command('man missing', stderr="No manual entry for missing\n"), ['missing --help']),
(Command('man 2 read'), 'man 3 read'), (Command('man 2 read'), 'man 3 read'),
(Command('man 3 read'), 'man 2 read'), (Command('man 3 read'), 'man 2 read'),
(Command('man -s2 read'), 'man -s3 read'), (Command('man -s2 read'), 'man -s3 read'),

View File

@@ -0,0 +1,30 @@
import pytest
from thefuck.rules.missing_space_before_subcommand import (
match, get_new_command)
from tests.utils import Command
@pytest.fixture(autouse=True)
def all_executables(mocker):
return mocker.patch(
'thefuck.rules.missing_space_before_subcommand.get_all_executables',
return_value=['git', 'ls', 'npm'])
@pytest.mark.parametrize('script', [
'gitbranch', 'ls-la', 'npminstall'])
def test_match(script):
assert match(Command(script))
@pytest.mark.parametrize('script', ['git branch', 'vimfile'])
def test_not_match(script):
assert not match(Command(script))
@pytest.mark.parametrize('script, result', [
('gitbranch', 'git branch'),
('ls-la', 'ls -la'),
('npminstall webpack', 'npm install webpack')])
def test_get_new_command(script, result):
assert get_new_command(Command(script)) == result

View File

@@ -25,7 +25,7 @@ def test_match(command):
[INFO] Finished at: Wed Aug 26 13:05:47 BST 2015 [INFO] Finished at: Wed Aug 26 13:05:47 BST 2015
[INFO] Final Memory: 6M/240M [INFO] Final Memory: 6M/240M
[INFO] ------------------------------------------------------------------------ [INFO] ------------------------------------------------------------------------
"""), """), # noqa
Command(script='mvn --help'), Command(script='mvn --help'),
Command(script='mvn -v') Command(script='mvn -v')
]) ])

View File

@@ -25,7 +25,7 @@ def test_match(command):
[INFO] Finished at: Wed Aug 26 13:05:47 BST 2015 [INFO] Finished at: Wed Aug 26 13:05:47 BST 2015
[INFO] Final Memory: 6M/240M [INFO] Final Memory: 6M/240M
[INFO] ------------------------------------------------------------------------ [INFO] ------------------------------------------------------------------------
"""), """), # noqa
Command(script='mvn --help'), Command(script='mvn --help'),
Command(script='mvn -v') Command(script='mvn -v')
]) ])

View File

@@ -0,0 +1,43 @@
import pytest
from thefuck.rules.path_from_history import match, get_new_command
from tests.utils import Command
@pytest.fixture(autouse=True)
def history(mocker):
return mocker.patch(
'thefuck.rules.path_from_history.get_valid_history_without_current',
return_value=['cd /opt/java', 'ls ~/work/project/'])
@pytest.fixture(autouse=True)
def path_exists(mocker):
path_mock = mocker.patch('thefuck.rules.path_from_history.Path')
exists_mock = path_mock.return_value.expanduser.return_value.exists
exists_mock.return_value = True
return exists_mock
@pytest.mark.parametrize('script, stderr', [
('ls project', 'no such file or directory: project'),
('cd project', "can't cd to project"),
])
def test_match(script, stderr):
assert match(Command(script, stderr=stderr))
@pytest.mark.parametrize('script, stderr', [
('myapp cats', 'no such file or directory: project'),
('cd project', ""),
])
def test_not_match(script, stderr):
assert not match(Command(script, stderr=stderr))
@pytest.mark.parametrize('script, stderr, result', [
('ls project', 'no such file or directory: project', 'ls ~/work/project'),
('cd java', "can't cd to java", 'cd /opt/java'),
])
def test_get_new_command(script, stderr, result):
new_command = get_new_command(Command(script, stderr=stderr))
assert new_command[0] == result

View File

@@ -8,5 +8,5 @@ def test_match():
def test_get_new_command(): def test_get_new_command():
assert get_new_command(Command('./test_sudo.py'))\ assert (get_new_command(Command('./test_sudo.py'))
== 'python ./test_sudo.py' == 'python ./test_sudo.py')

View File

@@ -0,0 +1,17 @@
import pytest
from thefuck.rules.remove_trailing_cedilla import match, get_new_command, CEDILLA
from tests.utils import Command
@pytest.mark.parametrize('command', [
Command(script='wrong' + CEDILLA),
Command(script='wrong with args' + CEDILLA)])
def test_match(command):
assert match(command)
@pytest.mark.parametrize('command, new_command', [
(Command('wrong' + CEDILLA), 'wrong'),
(Command('wrong with args' + CEDILLA), 'wrong with args')])
def test_get_new_command(command, new_command):
assert get_new_command(command) == new_command

View File

@@ -17,5 +17,5 @@ def test_not_match(command):
def test_get_new_command(): def test_get_new_command():
assert get_new_command(Command(script='rm -rf /')) \ assert (get_new_command(Command(script='rm -rf /'))
== 'rm -rf / --no-preserve-root' == 'rm -rf / --no-preserve-root')

View File

@@ -0,0 +1,46 @@
import pytest
from thefuck.rules.scm_correction import match, get_new_command
from tests.utils import Command
@pytest.fixture
def get_actual_scm_mock(mocker):
return mocker.patch('thefuck.rules.scm_correction._get_actual_scm',
return_value=None)
@pytest.mark.parametrize('script, stderr, actual_scm', [
('git log', 'fatal: Not a git repository '
'(or any of the parent directories): .git',
'hg'),
('hg log', "abort: no repository found in '/home/nvbn/exp/thefuck' "
"(.hg not found)!",
'git')])
def test_match(get_actual_scm_mock, script, stderr, actual_scm):
get_actual_scm_mock.return_value = actual_scm
assert match(Command(script, stderr=stderr))
@pytest.mark.parametrize('script, stderr, actual_scm', [
('git log', '', 'hg'),
('git log', 'fatal: Not a git repository '
'(or any of the parent directories): .git',
None),
('hg log', "abort: no repository found in '/home/nvbn/exp/thefuck' "
"(.hg not found)!",
None),
('not-scm log', "abort: no repository found in '/home/nvbn/exp/thefuck' "
"(.hg not found)!",
'git')])
def test_not_match(get_actual_scm_mock, script, stderr, actual_scm):
get_actual_scm_mock.return_value = actual_scm
assert not match(Command(script, stderr=stderr))
@pytest.mark.parametrize('script, actual_scm, result', [
('git log', 'hg', 'hg log'),
('hg log', 'git', 'git log')])
def test_get_new_command(get_actual_scm_mock, script, actual_scm, result):
get_actual_scm_mock.return_value = actual_scm
new_command = get_new_command(Command(script))
assert new_command == result

View File

@@ -18,11 +18,11 @@ def test_match(sed_unterminated_s):
def test_get_new_command(sed_unterminated_s): def test_get_new_command(sed_unterminated_s):
assert get_new_command(Command('sed -e s/foo/bar', stderr=sed_unterminated_s)) \ assert (get_new_command(Command('sed -e s/foo/bar', stderr=sed_unterminated_s))
== 'sed -e s/foo/bar/' == 'sed -e s/foo/bar/')
assert get_new_command(Command('sed -es/foo/bar', stderr=sed_unterminated_s)) \ assert (get_new_command(Command('sed -es/foo/bar', stderr=sed_unterminated_s))
== 'sed -es/foo/bar/' == 'sed -es/foo/bar/')
assert get_new_command(Command(r"sed -e 's/\/foo/bar'", stderr=sed_unterminated_s)) \ assert (get_new_command(Command(r"sed -e 's/\/foo/bar'", stderr=sed_unterminated_s))
== r"sed -e 's/\/foo/bar/'" == r"sed -e 's/\/foo/bar/'")
assert get_new_command(Command(r"sed -e s/foo/bar -es/baz/quz", stderr=sed_unterminated_s)) \ assert (get_new_command(Command(r"sed -e s/foo/bar -es/baz/quz", stderr=sed_unterminated_s))
== r"sed -e s/foo/bar/ -es/baz/quz/" == r"sed -e s/foo/bar/ -es/baz/quz/")

View File

@@ -0,0 +1,39 @@
import pytest
from thefuck.rules.sudo_command_from_user_path import match, get_new_command
from tests.utils import Command
stderr = 'sudo: {}: command not found'
@pytest.fixture(autouse=True)
def which(mocker):
return mocker.patch('thefuck.rules.sudo_command_from_user_path.which',
return_value='/usr/bin/app')
@pytest.mark.parametrize('script, stderr', [
('sudo npm install -g react-native-cli', stderr.format('npm')),
('sudo -u app appcfg update .', stderr.format('appcfg'))])
def test_match(script, stderr):
assert match(Command(script, stderr=stderr))
@pytest.mark.parametrize('script, stderr, which_result', [
('npm --version', stderr.format('npm'), '/usr/bin/npm'),
('sudo npm --version', '', '/usr/bin/npm'),
('sudo npm --version', stderr.format('npm'), None)])
def test_not_match(which, script, stderr, which_result):
which.return_value = which_result
assert not match(Command(script, stderr=stderr))
@pytest.mark.parametrize('script, stderr, result', [
('sudo npm install -g react-native-cli',
stderr.format('npm'),
'sudo env "PATH=$PATH" npm install -g react-native-cli'),
('sudo -u app appcfg update .',
stderr.format('appcfg'),
'sudo -u app env "PATH=$PATH" appcfg update .')])
def test_get_new_command(script, stderr, result):
assert get_new_command(Command(script, stderr=stderr)) == result

View File

@@ -23,12 +23,12 @@ def test_not_match(command):
@pytest.mark.parametrize('command, new_command', [ @pytest.mark.parametrize('command, new_command', [
(Command('hdfs dfs ls', (Command('hdfs dfs ls',
stderr='ls: Unknown command\nDid you mean -ls? This command begins with a dash.'), ['hdfs dfs -ls']), stderr='ls: Unknown command\nDid you mean -ls? This command begins with a dash.'), ['hdfs dfs -ls']),
(Command('hdfs dfs rm /foo/bar', (Command('hdfs dfs rm /foo/bar',
stderr='rm: Unknown command\nDid you mean -rm? This command begins with a dash.'), ['hdfs dfs -rm /foo/bar']), stderr='rm: Unknown command\nDid you mean -rm? This command begins with a dash.'), ['hdfs dfs -rm /foo/bar']),
(Command('./bin/hdfs dfs ls -R /foo/bar', (Command('./bin/hdfs dfs ls -R /foo/bar',
stderr='ls: Unknown command\nDid you mean -ls? This command begins with a dash.'), ['./bin/hdfs dfs -ls -R /foo/bar']), stderr='ls: Unknown command\nDid you mean -ls? This command begins with a dash.'), ['./bin/hdfs dfs -ls -R /foo/bar']),
(Command('./bin/hdfs dfs -Dtest=fred ls -R /foo/bar', (Command('./bin/hdfs dfs -Dtest=fred ls -R /foo/bar',
stderr='ls: Unknown command\nDid you mean -ls? This command begins with a dash.'), ['./bin/hdfs dfs -Dtest=fred -ls -R /foo/bar'])]) stderr='ls: Unknown command\nDid you mean -ls? This command begins with a dash.'), ['./bin/hdfs dfs -Dtest=fred -ls -R /foo/bar'])])
def test_get_new_command(command, new_command): def test_get_new_command(command, new_command):
assert get_new_command(command) == new_command assert get_new_command(command) == new_command

View File

@@ -27,8 +27,8 @@ def test_not_match(command):
(Command(script='vagrant ssh', stderr='VM must be running to open SSH connection. Run `vagrant up`\nto start the virtual machine.'), 'vagrant up && vagrant ssh'), (Command(script='vagrant ssh', stderr='VM must be running to open SSH connection. Run `vagrant up`\nto start the virtual machine.'), 'vagrant up && vagrant ssh'),
(Command(script='vagrant ssh devbox', stderr='VM must be running to open SSH connection. Run `vagrant up`\nto start the virtual machine.'), ['vagrant up devbox && vagrant ssh devbox', 'vagrant up && vagrant ssh devbox']), (Command(script='vagrant ssh devbox', stderr='VM must be running to open SSH connection. Run `vagrant up`\nto start the virtual machine.'), ['vagrant up devbox && vagrant ssh devbox', 'vagrant up && vagrant ssh devbox']),
(Command(script='vagrant rdp', (Command(script='vagrant rdp',
stderr='VM must be created before running this command. Run `vagrant up` first.'), 'vagrant up && vagrant rdp'), stderr='VM must be created before running this command. Run `vagrant up` first.'), 'vagrant up && vagrant rdp'),
(Command(script='vagrant rdp devbox', (Command(script='vagrant rdp devbox',
stderr='VM must be created before running this command. Run `vagrant up` first.'), ['vagrant up devbox && vagrant rdp devbox', 'vagrant up && vagrant rdp devbox'])]) stderr='VM must be created before running this command. Run `vagrant up` first.'), ['vagrant up devbox && vagrant rdp devbox', 'vagrant up && vagrant rdp devbox'])])
def test_get_new_command(command, new_command): def test_get_new_command(command, new_command):
assert get_new_command(command) == new_command assert get_new_command(command) == new_command

View File

@@ -0,0 +1,24 @@
import pytest
from thefuck.rules.yarn_alias import match, get_new_command
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)
@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

View File

@@ -0,0 +1,118 @@
# -*- encoding: utf-8 -*-
from io import BytesIO
import pytest
from tests.utils import Command
from thefuck.rules.yarn_command_not_found import match, get_new_command
stderr = '''
error Command "{}" not found.
'''.format
yarn_help_stdout = b'''
Usage: yarn [command] [flags]
Options:
-h, --help output usage information
-V, --version output the version number
--verbose output verbose messages on internal operations
--offline trigger an error if any required dependencies are not available in local cache
--prefer-offline use network only if dependencies are not available in local cache
--strict-semver
--json
--ignore-scripts don't run lifecycle scripts
--har save HAR output of network traffic
--ignore-platform ignore platform checks
--ignore-engines ignore engines check
--ignore-optional ignore optional dependencies
--force ignore all caches
--no-bin-links don't generate bin links when setting up packages
--flat only allow one version of a package
--prod, --production [prod]
--no-lockfile don't read or generate a lockfile
--pure-lockfile don't generate a lockfile
--frozen-lockfile don't generate a lockfile and fail if an update is needed
--link-duplicates create hardlinks to the repeated modules in node_modules
--global-folder <path>
--modules-folder <path> rather than installing modules into the node_modules folder relative to the cwd, output them here
--cache-folder <path> specify a custom folder to store the yarn cache
--mutex <type>[:specifier] use a mutex to ensure only one yarn instance is executing
--no-emoji disable emoji in output
--proxy <host>
--https-proxy <host>
--no-progress disable progress bar
--network-concurrency <number> maximum number of concurrent network requests
Commands:
- access
- add
- bin
- cache
- check
- clean
- config
- generate-lock-entry
- global
- import
- info
- init
- install
- licenses
- link
- list
- login
- logout
- outdated
- owner
- pack
- publish
- remove
- run
- tag
- team
- unlink
- upgrade
- upgrade-interactive
- version
- versions
- why
Run `yarn help COMMAND` for more information on specific commands.
Visit https://yarnpkg.com/en/docs/cli/ to learn more about Yarn.
''' # noqa
@pytest.fixture(autouse=True)
def yarn_help(mocker):
patch = mocker.patch('thefuck.rules.yarn_command_not_found.Popen')
patch.return_value.stdout = BytesIO(yarn_help_stdout)
return patch
@pytest.mark.parametrize('command', [
Command('yarn whyy webpack', stderr=stderr('whyy'))])
def test_match(command):
assert match(command)
@pytest.mark.parametrize('command', [
Command('npm nuild', stderr=stderr('nuild')),
Command('yarn install')])
def test_not_match(command):
assert not match(command)
@pytest.mark.parametrize('command, result', [
(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):
fixed_command = get_new_command(command)
if isinstance(fixed_command, list):
fixed_command = fixed_command[0]
assert fixed_command == result

View 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

View File

@@ -0,0 +1,57 @@
import pytest
from thefuck.rules.yarn_help import match, get_new_command
from tests.utils import Command
from thefuck.system import open_command
stdout_clean = '''
Usage: yarn [command] [flags]
Options:
-h, --help output usage information
-V, --version output the version number
--verbose output verbose messages on internal operations
--offline trigger an error if any required dependencies are not available in local cache
--prefer-offline use network only if dependencies are not available in local cache
--strict-semver
--json
--ignore-scripts don't run lifecycle scripts
--har save HAR output of network traffic
--ignore-platform ignore platform checks
--ignore-engines ignore engines check
--ignore-optional ignore optional dependencies
--force ignore all caches
--no-bin-links don't generate bin links when setting up packages
--flat only allow one version of a package
--prod, --production [prod]
--no-lockfile don't read or generate a lockfile
--pure-lockfile don't generate a lockfile
--frozen-lockfile don't generate a lockfile and fail if an update is needed
--link-duplicates create hardlinks to the repeated modules in node_modules
--global-folder <path>
--modules-folder <path> rather than installing modules into the node_modules folder relative to the cwd, output them here
--cache-folder <path> specify a custom folder to store the yarn cache
--mutex <type>[:specifier] use a mutex to ensure only one yarn instance is executing
--no-emoji disable emoji in output
--proxy <host>
--https-proxy <host>
--no-progress disable progress bar
--network-concurrency <number> maximum number of concurrent network requests
Visit https://yarnpkg.com/en/docs/cli/clean for documentation about this command.
''' # noqa
@pytest.mark.parametrize('command', [
Command(script='yarn help clean', stdout=stdout_clean)])
def test_match(command):
assert match(command)
@pytest.mark.parametrize('command, url', [
(Command('yarn help clean', stdout=stdout_clean),
'https://yarnpkg.com/en/docs/cli/clean')])
def test_get_new_command(command, url):
assert get_new_command(command) == open_command(url)

View File

@@ -20,3 +20,11 @@ def history_lines(mocker):
.return_value.readlines.return_value = lines .return_value.readlines.return_value = lines
return aux return aux
@pytest.fixture
def config_exists(mocker):
path_mock = mocker.patch('thefuck.shells.generic.Path')
return path_mock.return_value \
.expanduser.return_value \
.exists

View File

@@ -33,6 +33,9 @@ class TestBash(object):
def test_and_(self, shell): def test_and_(self, shell):
assert shell.and_('ls', 'cd') == 'ls && cd' 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): def test_get_aliases(self, shell):
assert shell.get_aliases() == {'fuck': 'eval $(thefuck $(fc -ln -1))', assert shell.get_aliases() == {'fuck': 'eval $(thefuck $(fc -ln -1))',
'l': 'ls -CF', 'l': 'ls -CF',
@@ -40,19 +43,32 @@ class TestBash(object):
'll': 'ls -alF'} 'll': 'ls -alF'}
def test_app_alias(self, shell): def test_app_alias(self, shell):
assert 'alias fuck' in shell.app_alias('fuck') assert 'fuck () {' in shell.app_alias('fuck')
assert 'alias FUCK' in shell.app_alias('FUCK') assert 'FUCK () {' in shell.app_alias('FUCK')
assert 'thefuck' in shell.app_alias('fuck') assert 'thefuck' in shell.app_alias('fuck')
assert 'TF_ALIAS=fuck' in shell.app_alias('fuck') assert 'PYTHONIOENCODING' in shell.app_alias('fuck')
assert 'PYTHONIOENCODING=utf-8' in shell.app_alias('fuck')
def test_app_alias_variables_correctly_set(self, shell): def test_app_alias_variables_correctly_set(self, shell):
alias = shell.app_alias('fuck') alias = shell.app_alias('fuck')
assert "alias fuck='TF_CMD=$(TF_ALIAS" in alias assert "fuck () {" in alias
assert '$(TF_ALIAS=fuck PYTHONIOENCODING' in alias assert "TF_ALIAS=fuck" in alias
assert 'PYTHONIOENCODING=utf-8 TF_SHELL_ALIASES' in alias assert 'PYTHONIOENCODING=utf-8' in alias
assert 'ALIASES=$(alias) thefuck' in alias assert 'TF_SHELL_ALIASES=$(alias)' in alias
def test_get_history(self, history_lines, shell): def test_get_history(self, history_lines, shell):
history_lines(['ls', 'rm']) history_lines(['ls', 'rm'])
assert list(shell.get_history()) == ['ls', 'rm'] assert list(shell.get_history()) == ['ls', 'rm']
def test_split_command(self, shell):
command = 'git log -p'
command_parts = ['git', 'log', '-p']
assert shell.split_command(command) == command_parts
def test_how_to_configure(self, shell, config_exists):
config_exists.return_value = True
assert shell.how_to_configure().can_configure_automatically
def test_how_to_configure_when_config_not_found(self, shell,
config_exists):
config_exists.return_value = False
assert not shell.how_to_configure().can_configure_automatically

View File

@@ -18,17 +18,14 @@ class TestFish(object):
b'man\nmath\npopd\npushd\nruby') b'man\nmath\npopd\npushd\nruby')
return mock return mock
@pytest.fixture
def os_environ(self, monkeypatch, key, value):
monkeypatch.setattr('os.environ', {key: value})
@pytest.mark.parametrize('key, value', [ @pytest.mark.parametrize('key, value', [
('TF_OVERRIDDEN_ALIASES', 'cut,git,sed'), # legacy ('TF_OVERRIDDEN_ALIASES', 'cut,git,sed'), # legacy
('THEFUCK_OVERRIDDEN_ALIASES', 'cut,git,sed'), ('THEFUCK_OVERRIDDEN_ALIASES', 'cut,git,sed'),
('THEFUCK_OVERRIDDEN_ALIASES', 'cut, git, sed'), ('THEFUCK_OVERRIDDEN_ALIASES', 'cut, git, sed'),
('THEFUCK_OVERRIDDEN_ALIASES', ' cut,\tgit,sed\n'), ('THEFUCK_OVERRIDDEN_ALIASES', ' cut,\tgit,sed\n'),
('THEFUCK_OVERRIDDEN_ALIASES', '\ncut,\n\ngit,\tsed\r')]) ('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', assert shell._get_overridden_aliases() == {'cd', 'cut', 'git', 'grep',
'ls', 'man', 'open', 'sed'} 'ls', 'man', 'open', 'sed'}
@@ -55,6 +52,9 @@ class TestFish(object):
def test_and_(self, shell): def test_and_(self, shell):
assert shell.and_('foo', 'bar') == 'foo; and bar' 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): def test_get_aliases(self, shell):
assert shell.get_aliases() == {'fish_config': 'fish_config', assert shell.get_aliases() == {'fish_config': 'fish_config',
'fuck': 'fuck', 'fuck': 'fuck',
@@ -76,11 +76,11 @@ class TestFish(object):
def test_app_alias_alter_history(self, settings, shell): def test_app_alias_alter_history(self, settings, shell):
settings.alter_history = True settings.alter_history = True
assert 'history --delete' in shell.app_alias('FUCK') assert 'builtin history delete' in shell.app_alias('FUCK')
assert 'history --merge' in shell.app_alias('FUCK') assert 'builtin history merge' in shell.app_alias('FUCK')
settings.alter_history = False settings.alter_history = False
assert 'history --delete' not in shell.app_alias('FUCK') assert 'builtin history delete' not in shell.app_alias('FUCK')
assert 'history --merge' not in shell.app_alias('FUCK') assert 'builtin history merge' not in shell.app_alias('FUCK')
def test_get_history(self, history_lines, shell): def test_get_history(self, history_lines, shell):
history_lines(['- cmd: ls', ' when: 1432613911', history_lines(['- cmd: ls', ' when: 1432613911',
@@ -95,3 +95,12 @@ class TestFish(object):
shell.put_to_history(entry) shell.put_to_history(entry)
builtins_open.return_value.__enter__.return_value. \ builtins_open.return_value.__enter__.return_value. \
write.assert_called_once_with(entry_utf8) write.assert_called_once_with(entry_utf8)
def test_how_to_configure(self, shell, config_exists):
config_exists.return_value = True
assert shell.how_to_configure().can_configure_automatically
def test_how_to_configure_when_config_not_found(self, shell,
config_exists):
config_exists.return_value = False
assert not shell.how_to_configure().can_configure_automatically

View File

@@ -18,6 +18,9 @@ class TestGeneric(object):
def test_and_(self, shell): def test_and_(self, shell):
assert shell.and_('ls', 'cd') == 'ls && cd' 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): def test_get_aliases(self, shell):
assert shell.get_aliases() == {} assert shell.get_aliases() == {}
@@ -37,3 +40,6 @@ class TestGeneric(object):
def test_split_command(self, shell): def test_split_command(self, shell):
assert shell.split_command('ls') == ['ls'] assert shell.split_command('ls') == ['ls']
assert shell.split_command(u'echo café') == [u'echo', u'café'] assert shell.split_command(u'echo café') == [u'echo', u'café']
def test_how_to_configure(self, shell):
assert shell.how_to_configure() is None

View File

@@ -17,3 +17,6 @@ class TestPowershell(object):
assert 'function fuck' in shell.app_alias('fuck') assert 'function fuck' in shell.app_alias('fuck')
assert 'function FUCK' in shell.app_alias('FUCK') assert 'function FUCK' in shell.app_alias('FUCK')
assert 'thefuck' in shell.app_alias('fuck') assert 'thefuck' in shell.app_alias('fuck')
def test_how_to_configure(self, shell):
assert not shell.how_to_configure().can_configure_automatically

View File

@@ -34,6 +34,9 @@ class TestTcsh(object):
def test_and_(self, shell): def test_and_(self, shell):
assert shell.and_('ls', 'cd') == 'ls && cd' 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): def test_get_aliases(self, shell):
assert shell.get_aliases() == {'fuck': 'eval $(thefuck $(fc -ln -1))', assert shell.get_aliases() == {'fuck': 'eval $(thefuck $(fc -ln -1))',
'l': 'ls -CF', 'l': 'ls -CF',
@@ -48,3 +51,12 @@ class TestTcsh(object):
def test_get_history(self, history_lines, shell): def test_get_history(self, history_lines, shell):
history_lines(['ls', 'rm']) history_lines(['ls', 'rm'])
assert list(shell.get_history()) == ['ls', 'rm'] assert list(shell.get_history()) == ['ls', 'rm']
def test_how_to_configure(self, shell, config_exists):
config_exists.return_value = True
assert shell.how_to_configure().can_configure_automatically
def test_how_to_configure_when_config_not_found(self, shell,
config_exists):
config_exists.return_value = False
assert not shell.how_to_configure().can_configure_automatically

View File

@@ -32,6 +32,9 @@ class TestZsh(object):
def test_and_(self, shell): def test_and_(self, shell):
assert shell.and_('ls', 'cd') == 'ls && cd' 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): def test_get_aliases(self, shell):
assert shell.get_aliases() == { assert shell.get_aliases() == {
'fuck': 'eval $(thefuck $(fc -ln -1 | tail -n 1))', 'fuck': 'eval $(thefuck $(fc -ln -1 | tail -n 1))',
@@ -40,18 +43,27 @@ class TestZsh(object):
'll': 'ls -alF'} 'll': 'ls -alF'}
def test_app_alias(self, shell): def test_app_alias(self, shell):
assert 'alias fuck' in shell.app_alias('fuck') assert 'fuck () {' in shell.app_alias('fuck')
assert 'alias FUCK' in shell.app_alias('FUCK') assert 'FUCK () {' in shell.app_alias('FUCK')
assert 'thefuck' in shell.app_alias('fuck') assert 'thefuck' in shell.app_alias('fuck')
assert 'PYTHONIOENCODING' in shell.app_alias('fuck') assert 'PYTHONIOENCODING' in shell.app_alias('fuck')
def test_app_alias_variables_correctly_set(self, shell): def test_app_alias_variables_correctly_set(self, shell):
alias = shell.app_alias('fuck') alias = shell.app_alias('fuck')
assert "alias fuck='TF_CMD=$(TF_ALIAS" in alias assert "fuck () {" in alias
assert '$(TF_ALIAS=fuck PYTHONIOENCODING' in alias assert "TF_ALIAS=fuck" in alias
assert 'PYTHONIOENCODING=utf-8 TF_SHELL_ALIASES' in alias assert 'PYTHONIOENCODING=utf-8' in alias
assert 'ALIASES=$(alias) thefuck' in alias assert 'TF_SHELL_ALIASES=$(alias)' in alias
def test_get_history(self, history_lines, shell): def test_get_history(self, history_lines, shell):
history_lines([': 1432613911:0;ls', ': 1432613916:0;rm']) history_lines([': 1432613911:0;ls', ': 1432613916:0;rm'])
assert list(shell.get_history()) == ['ls', 'rm'] assert list(shell.get_history()) == ['ls', 'rm']
def test_how_to_configure(self, shell, config_exists):
config_exists.return_value = True
assert shell.how_to_configure().can_configure_automatically
def test_how_to_configure_when_config_not_found(self, shell,
config_exists):
config_exists.return_value = False
assert not shell.how_to_configure().can_configure_automatically

View 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

View File

@@ -1,5 +1,6 @@
import pytest import pytest
import six import six
import os
from mock import Mock from mock import Mock
from thefuck import const from thefuck import const
@@ -9,14 +10,6 @@ def load_source(mocker):
return mocker.patch('thefuck.conf.load_source') 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): def test_settings_defaults(load_source, settings):
load_source.return_value = object() load_source.return_value = object()
settings.init() settings.init()
@@ -24,7 +17,6 @@ def test_settings_defaults(load_source, settings):
assert getattr(settings, key) == val assert getattr(settings, key) == val
@pytest.mark.usefixture('environ')
class TestSettingsFromFile(object): class TestSettingsFromFile(object):
def test_from_file(self, load_source, settings): def test_from_file(self, load_source, settings):
load_source.return_value = Mock(rules=['test'], load_source.return_value = Mock(rules=['test'],
@@ -53,15 +45,15 @@ class TestSettingsFromFile(object):
@pytest.mark.usefixture('load_source') @pytest.mark.usefixture('load_source')
class TestSettingsFromEnv(object): class TestSettingsFromEnv(object):
def test_from_env(self, environ, settings): def test_from_env(self, os_environ, settings):
environ.update({'THEFUCK_RULES': 'bash:lisp', os_environ.update({'THEFUCK_RULES': 'bash:lisp',
'THEFUCK_EXCLUDE_RULES': 'git:vim', 'THEFUCK_EXCLUDE_RULES': 'git:vim',
'THEFUCK_WAIT_COMMAND': '55', 'THEFUCK_WAIT_COMMAND': '55',
'THEFUCK_REQUIRE_CONFIRMATION': 'true', 'THEFUCK_REQUIRE_CONFIRMATION': 'true',
'THEFUCK_NO_COLORS': 'false', 'THEFUCK_NO_COLORS': 'false',
'THEFUCK_PRIORITY': 'bash=10:lisp=wrong:vim=15', 'THEFUCK_PRIORITY': 'bash=10:lisp=wrong:vim=15',
'THEFUCK_WAIT_SLOW_COMMAND': '999', 'THEFUCK_WAIT_SLOW_COMMAND': '999',
'THEFUCK_SLOW_COMMANDS': 'lein:react-native:./gradlew'}) 'THEFUCK_SLOW_COMMANDS': 'lein:react-native:./gradlew'})
settings.init() settings.init()
assert settings.rules == ['bash', 'lisp'] assert settings.rules == ['bash', 'lisp']
assert settings.exclude_rules == ['git', 'vim'] assert settings.exclude_rules == ['git', 'vim']
@@ -72,12 +64,19 @@ class TestSettingsFromEnv(object):
assert settings.wait_slow_command == 999 assert settings.wait_slow_command == 999
assert settings.slow_commands == ['lein', 'react-native', './gradlew'] assert settings.slow_commands == ['lein', 'react-native', './gradlew']
def test_from_env_with_DEFAULT(self, environ, settings): def test_from_env_with_DEFAULT(self, os_environ, settings):
environ.update({'THEFUCK_RULES': 'DEFAULT_RULES:bash:lisp'}) os_environ.update({'THEFUCK_RULES': 'DEFAULT_RULES:bash:lisp'})
settings.init() settings.init()
assert settings.rules == const.DEFAULT_RULES + ['bash', 'lisp'] 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): class TestInitializeSettingsFile(object):
def test_ignore_if_exists(self, settings): def test_ignore_if_exists(self, settings):
settings_path_mock = Mock(is_file=Mock(return_value=True), open=Mock()) settings_path_mock = Mock(is_file=Mock(return_value=True), open=Mock())
@@ -101,3 +100,22 @@ class TestInitializeSettingsFile(object):
for setting in const.DEFAULT_SETTINGS.items(): for setting in const.DEFAULT_SETTINGS.items():
assert '# {} = {}\n'.format(*setting) in settings_file_contents assert '# {} = {}\n'.format(*setting) in settings_file_contents
settings_file.close() settings_file.close()
@pytest.mark.parametrize('legacy_dir_exists, xdg_config_home, result', [
(False, '~/.config', '~/.config/thefuck'),
(False, '/user/test/config/', '/user/test/config/thefuck'),
(True, '~/.config', '~/.thefuck'),
(True, '/user/test/config/', '~/.thefuck')])
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:
os_environ['XDG_CONFIG_HOME'] = xdg_config_home
else:
os_environ.pop('XDG_CONFIG_HOME', None)
path = settings._get_user_dir_path().as_posix()
assert path == os.path.expanduser(result)

View File

@@ -1,13 +1,8 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import pytest import pytest
try:
from pathlib import Path
pathlib_name = 'pathlib'
except ImportError:
from pathlib2 import Path
pathlib_name = 'pathlib2'
from thefuck import corrector, const from thefuck import corrector, const
from thefuck.system import Path
from tests.utils import Rule, Command, CorrectedCommand from tests.utils import Rule, Command, CorrectedCommand
from thefuck.corrector import get_corrected_commands, organize_commands from thefuck.corrector import get_corrected_commands, organize_commands
@@ -16,7 +11,7 @@ class TestGetRules(object):
@pytest.fixture @pytest.fixture
def glob(self, mocker): def glob(self, mocker):
results = {} results = {}
mocker.patch(pathlib_name + '.Path.glob', mocker.patch('thefuck.system.Path.glob',
new_callable=lambda: lambda *_: results.pop('value', [])) new_callable=lambda: lambda *_: results.pop('value', []))
return lambda value: results.update({'value': value}) return lambda value: results.update({'value': value})
@@ -52,8 +47,8 @@ def test_get_corrected_commands(mocker):
get_new_command=lambda x: [x.script + '@', x.script + ';'], get_new_command=lambda x: [x.script + '@', x.script + ';'],
priority=60)] priority=60)]
mocker.patch('thefuck.corrector.get_rules', return_value=rules) mocker.patch('thefuck.corrector.get_rules', return_value=rules)
assert [cmd.script for cmd in get_corrected_commands(command)] \ assert ([cmd.script for cmd in get_corrected_commands(command)]
== ['test!', 'test@', 'test;'] == ['test!', 'test@', 'test;'])
def test_organize_commands(): def test_organize_commands():

View File

@@ -0,0 +1,140 @@
import pytest
import json
from six import StringIO
from mock import MagicMock
from thefuck.shells.generic import ShellConfiguration
from thefuck.not_configured import main
@pytest.fixture(autouse=True)
def usage_tracker(mocker):
return mocker.patch(
'thefuck.not_configured._get_not_configured_usage_tracker_path',
new_callable=MagicMock)
@pytest.fixture(autouse=True)
def usage_tracker_io(usage_tracker):
io = StringIO()
usage_tracker.return_value \
.open.return_value \
.__enter__.return_value = io
return io
@pytest.fixture(autouse=True)
def usage_tracker_exists(usage_tracker):
usage_tracker.return_value \
.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)
def shell_pid(mocker):
return mocker.patch('thefuck.not_configured._get_shell_pid',
new_callable=MagicMock)
@pytest.fixture(autouse=True)
def shell(mocker):
shell = mocker.patch('thefuck.not_configured.shell',
new_callable=MagicMock)
shell.get_history.return_value = []
shell.how_to_configure.return_value = ShellConfiguration(
content='eval $(thefuck --alias)',
path='/tmp/.bashrc',
reload='bash',
can_configure_automatically=True)
return shell
@pytest.fixture(autouse=True)
def shell_config(mocker):
path_mock = mocker.patch('thefuck.not_configured.Path',
new_callable=MagicMock)
return path_mock.return_value \
.expanduser.return_value \
.open.return_value \
.__enter__.return_value
@pytest.fixture(autouse=True)
def logs(mocker):
return mocker.patch('thefuck.not_configured.logs',
new_callable=MagicMock)
def test_for_generic_shell(shell, logs):
shell.how_to_configure.return_value = None
main()
logs.how_to_configure_alias.assert_called_once()
def test_on_first_run(usage_tracker_io, usage_tracker_exists, shell_pid, logs):
shell_pid.return_value = 12
main()
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_io, shell_pid, shell, logs):
shell_pid.return_value = 12
shell.get_history.return_value = ['fuck', 'ls']
_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_io, shell_pid,
shell, logs):
shell.get_history.return_value = ['fuck']
shell_pid.return_value = 12
main()
_assert_tracker_updated(usage_tracker_io, 12)
logs.how_to_configure_alias.assert_called_once()
def test_when_cant_configure_automatically(shell_pid, shell, logs):
shell_pid.return_value = 12
shell.how_to_configure.return_value = ShellConfiguration(
content='eval $(thefuck --alias)',
path='/tmp/.bashrc',
reload='bash',
can_configure_automatically=False)
main()
logs.how_to_configure_alias.assert_called_once()
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_io, 12)
shell_config.read.return_value = 'eval $(thefuck --alias)'
main()
logs.already_configured.assert_called_once()
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_io, 12)
shell_config.read.return_value = ''
main()
shell_config.write.assert_any_call('eval $(thefuck --alias)')
logs.configured_successfully.assert_called_once()

View File

@@ -3,23 +3,20 @@
import os import os
from subprocess import PIPE from subprocess import PIPE
from mock import Mock from mock import Mock
try:
from pathlib import Path
except ImportError:
from pathlib2 import Path
import pytest import pytest
from tests.utils import CorrectedCommand, Rule, Command from tests.utils import CorrectedCommand, Rule, Command
from thefuck import const from thefuck import const
from thefuck.exceptions import EmptyCommand from thefuck.exceptions import EmptyCommand
from thefuck.system import Path
class TestCorrectedCommand(object): class TestCorrectedCommand(object):
def test_equality(self): def test_equality(self):
assert CorrectedCommand('ls', None, 100) == \ assert (CorrectedCommand('ls', None, 100) ==
CorrectedCommand('ls', None, 200) CorrectedCommand('ls', None, 200))
assert CorrectedCommand('ls', None, 100) != \ assert (CorrectedCommand('ls', None, 100) !=
CorrectedCommand('ls', lambda *_: _, 100) CorrectedCommand('ls', lambda *_: _, 100))
def test_hashable(self): def test_hashable(self):
assert {CorrectedCommand('ls', None, 100), assert {CorrectedCommand('ls', None, 100),
@@ -31,6 +28,20 @@ class TestCorrectedCommand(object):
assert u'{}'.format(CorrectedCommand(u'echo café', None, 100)) == \ assert u'{}'.format(CorrectedCommand(u'echo café', None, 100)) == \
u'CorrectedCommand(script=echo café, side_effect=None, priority=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): class TestRule(object):
def test_from_path(self, mocker): def test_from_path(self, mocker):
@@ -44,8 +55,8 @@ class TestRule(object):
priority=900, priority=900,
requires_output=True)) requires_output=True))
rule_path = os.path.join(os.sep, 'rules', 'bash.py') rule_path = os.path.join(os.sep, 'rules', 'bash.py')
assert Rule.from_path(Path(rule_path)) \ assert (Rule.from_path(Path(rule_path))
== Rule('bash', match, get_new_command, priority=900) == Rule('bash', match, get_new_command, priority=900))
load_source.assert_called_once_with('bash', rule_path) load_source.assert_called_once_with('bash', rule_path)
@pytest.mark.parametrize('rules, exclude_rules, rule, is_enabled', [ @pytest.mark.parametrize('rules, exclude_rules, rule, is_enabled', [
@@ -82,15 +93,15 @@ class TestRule(object):
def test_get_corrected_commands_with_rule_returns_list(self): def test_get_corrected_commands_with_rule_returns_list(self):
rule = Rule(get_new_command=lambda x: [x.script + '!', x.script + '@'], rule = Rule(get_new_command=lambda x: [x.script + '!', x.script + '@'],
priority=100) priority=100)
assert list(rule.get_corrected_commands(Command(script='test'))) \ assert (list(rule.get_corrected_commands(Command(script='test')))
== [CorrectedCommand(script='test!', priority=100), == [CorrectedCommand(script='test!', priority=100),
CorrectedCommand(script='test@', priority=200)] CorrectedCommand(script='test@', priority=200)])
def test_get_corrected_commands_with_rule_returns_command(self): def test_get_corrected_commands_with_rule_returns_command(self):
rule = Rule(get_new_command=lambda x: x.script + '!', rule = Rule(get_new_command=lambda x: x.script + '!',
priority=100) priority=100)
assert list(rule.get_corrected_commands(Command(script='test'))) \ assert (list(rule.get_corrected_commands(Command(script='test')))
== [CorrectedCommand(script='test!', priority=100)] == [CorrectedCommand(script='test!', priority=100)])
class TestCommand(object): class TestCommand(object):
@@ -99,16 +110,15 @@ class TestCommand(object):
Popen = Mock() Popen = Mock()
Popen.return_value.stdout.read.return_value = b'stdout' Popen.return_value.stdout.read.return_value = b'stdout'
Popen.return_value.stderr.read.return_value = b'stderr' 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 return Popen
@pytest.fixture(autouse=True) @pytest.fixture(autouse=True)
def prepare(self, monkeypatch): def prepare(self, monkeypatch):
monkeypatch.setattr('thefuck.types.os.environ', {}) monkeypatch.setattr('thefuck.output_readers.rerun._wait_output',
monkeypatch.setattr('thefuck.types.Command._wait_output', lambda *_: True)
staticmethod(lambda *_: True))
def test_from_script_calls(self, Popen, settings): def test_from_script_calls(self, Popen, settings, os_environ):
settings.env = {} settings.env = {}
assert Command.from_raw_script( assert Command.from_raw_script(
['apt-get', 'search', 'vim']) == Command( ['apt-get', 'search', 'vim']) == Command(
@@ -118,7 +128,7 @@ class TestCommand(object):
stdin=PIPE, stdin=PIPE,
stdout=PIPE, stdout=PIPE,
stderr=PIPE, stderr=PIPE,
env={}) env=os_environ)
@pytest.mark.parametrize('script, result', [ @pytest.mark.parametrize('script, result', [
([''], None), ([''], None),

View File

@@ -30,11 +30,11 @@ def test_read_actions(patch_get_key):
const.KEY_DOWN, 'j', const.KEY_DOWN, 'j',
# Ctrl+C: # Ctrl+C:
const.KEY_CTRL_C, 'q']) const.KEY_CTRL_C, 'q'])
assert list(islice(ui.read_actions(), 8)) \ assert (list(islice(ui.read_actions(), 8))
== [const.ACTION_SELECT, const.ACTION_SELECT, == [const.ACTION_SELECT, const.ACTION_SELECT,
const.ACTION_PREVIOUS, const.ACTION_PREVIOUS, const.ACTION_PREVIOUS, const.ACTION_PREVIOUS,
const.ACTION_NEXT, const.ACTION_NEXT, const.ACTION_NEXT, const.ACTION_NEXT,
const.ACTION_ABORT, const.ACTION_ABORT] const.ACTION_ABORT, const.ACTION_ABORT])
def test_command_selector(): def test_command_selector():
@@ -69,34 +69,40 @@ class TestSelectCommand(object):
def test_without_confirmation(self, capsys, commands, settings): def test_without_confirmation(self, capsys, commands, settings):
settings.require_confirmation = False settings.require_confirmation = False
assert ui.select_command(iter(commands)) == commands[0] 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( def test_without_confirmation_with_side_effects(
self, capsys, commands_with_side_effect, settings): self, capsys, commands_with_side_effect, settings):
settings.require_confirmation = False settings.require_confirmation = False
assert ui.select_command(iter(commands_with_side_effect)) \ assert (ui.select_command(iter(commands_with_side_effect))
== commands_with_side_effect[0] == 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): def test_with_confirmation(self, capsys, patch_get_key, commands):
patch_get_key(['\n']) patch_get_key(['\n'])
assert ui.select_command(iter(commands)) == commands[0] 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): def test_with_confirmation_abort(self, capsys, patch_get_key, commands):
patch_get_key([const.KEY_CTRL_C]) patch_get_key([const.KEY_CTRL_C])
assert ui.select_command(iter(commands)) is None 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, def test_with_confirmation_with_side_effct(self, capsys, patch_get_key,
commands_with_side_effect): commands_with_side_effect):
patch_get_key(['\n']) patch_get_key(['\n'])
assert ui.select_command(iter(commands_with_side_effect)) \ assert (ui.select_command(iter(commands_with_side_effect))
== commands_with_side_effect[0] == 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): def test_with_confirmation_select_second(self, capsys, patch_get_key, commands):
patch_get_key([const.KEY_DOWN, '\n']) patch_get_key([const.KEY_DOWN, '\n'])
assert ui.select_command(iter(commands)) == commands[1] assert ui.select_command(iter(commands)) == commands[1]
assert capsys.readouterr() == ( stderr = (
'', u'\x1b[1K\rls [enter/↑/↓/ctrl+c]\x1b[1K\rcd [enter/↑/↓/ctrl+c]\n') 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)

View File

@@ -6,7 +6,7 @@ from mock import Mock
import six import six
from thefuck.utils import default_settings, \ from thefuck.utils import default_settings, \
memoize, get_closest, get_all_executables, replace_argument, \ memoize, get_closest, get_all_executables, replace_argument, \
get_all_matched_commands, is_app, for_app, cache, compatibility_call, \ get_all_matched_commands, is_app, for_app, cache, \
get_valid_history_without_current get_valid_history_without_current
from tests.utils import Command from tests.utils import Command
@@ -18,8 +18,7 @@ from tests.utils import Command
def test_default_settings(settings, override, old, new): def test_default_settings(settings, override, old, new):
settings.clear() settings.clear()
settings.update(old) settings.update(old)
fn = lambda _: _ default_settings(override)(lambda _: _)(None)
default_settings(override)(fn)(None)
assert settings == new assert settings == new
@@ -188,56 +187,6 @@ class TestCache(object):
assert shelve == {key: {'etag': '0', 'value': 'test'}} assert shelve == {key: {'etag': '0', 'value': 'test'}}
class TestCompatibilityCall(object):
def test_match(self):
def match(command):
assert command == Command()
return True
assert compatibility_call(match, Command())
def test_old_match(self, settings):
def match(command, _settings):
assert command == Command()
assert settings == _settings
return True
with pytest.warns(UserWarning):
assert compatibility_call(match, Command())
def test_get_new_command(self):
def get_new_command(command):
assert command == Command()
return True
assert compatibility_call(get_new_command, Command())
def test_old_get_new_command(self, settings):
def get_new_command(command, _settings):
assert command == Command()
assert settings == _settings
return True
with pytest.warns(UserWarning):
assert compatibility_call(get_new_command, Command())
def test_side_effect(self):
def side_effect(command, new_command):
assert command == Command() == new_command
return True
assert compatibility_call(side_effect, Command(), Command())
def test_old_side_effect(self, settings):
def side_effect(command, new_command, _settings):
assert command == Command() == new_command
assert settings == _settings
return True
with pytest.warns(UserWarning):
assert compatibility_call(side_effect, Command(), Command())
class TestGetValidHistoryWithoutCurrent(object): class TestGetValidHistoryWithoutCurrent(object):
@pytest.yield_fixture(autouse=True) @pytest.yield_fixture(autouse=True)
def fail_on_warning(self): def fail_on_warning(self):
@@ -257,8 +206,7 @@ class TestGetValidHistoryWithoutCurrent(object):
return_value='fuck') return_value='fuck')
@pytest.fixture(autouse=True) @pytest.fixture(autouse=True)
def bins(self, mocker, monkeypatch): def bins(self, mocker):
monkeypatch.setattr('thefuck.conf.os.environ', {'PATH': 'path'})
callables = list() callables = list()
for name in ['diff', 'ls', 'café']: for name in ['diff', 'ls', 'café']:
bin_mock = mocker.Mock(name=name) bin_mock = mocker.Mock(name=name)

View 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)

View File

@@ -1,12 +1,10 @@
from imp import load_source from imp import load_source
import os import os
import sys import sys
try: from warnings import warn
from pathlib import Path
except ImportError:
from pathlib2 import Path
from six import text_type from six import text_type
from . import const from . import const
from .system import Path
class Settings(dict): class Settings(dict):
@@ -16,7 +14,7 @@ class Settings(dict):
def __setattr__(self, key, value): def __setattr__(self, key, value):
self[key] = value self[key] = value
def init(self): def init(self, args=None):
"""Fills `settings` with values from `settings.py` and env.""" """Fills `settings` with values from `settings.py` and env."""
from .logs import exception from .logs import exception
@@ -33,6 +31,8 @@ class Settings(dict):
except Exception: except Exception:
exception("Can't load settings from env", sys.exc_info()) exception("Can't load settings from env", sys.exc_info())
self.update(self._settings_from_args(args))
def _init_settings_file(self): def _init_settings_file(self):
settings_path = self.user_dir.joinpath('settings.py') settings_path = self.user_dir.joinpath('settings.py')
if not settings_path.is_file(): if not settings_path.is_file():
@@ -42,15 +42,18 @@ class Settings(dict):
settings_file.write(u'# {} = {}\n'.format(*setting)) settings_file.write(u'# {} = {}\n'.format(*setting))
def _get_user_dir_path(self): def _get_user_dir_path(self):
# for backward compatibility, use `~/.thefuck` if it exists """Returns Path object representing the user config resource"""
legacy_user_dir = Path(os.path.expanduser('~/.thefuck')) xdg_config_home = os.environ.get('XDG_CONFIG_HOME', '~/.config')
user_dir = Path(xdg_config_home, 'thefuck').expanduser()
legacy_user_dir = Path('~', '.thefuck').expanduser()
# For backward compatibility use legacy '~/.thefuck' if it exists:
if legacy_user_dir.is_dir(): if legacy_user_dir.is_dir():
warn(u'Config path {} is deprecated. Please move to {}'.format(
legacy_user_dir, user_dir))
return legacy_user_dir return legacy_user_dir
else: else:
default_xdg_config_dir = os.path.expanduser("~/.config") return user_dir
xdg_config_dir = os.getenv("XDG_CONFIG_HOME", default_xdg_config_dir)
return Path(os.path.join(xdg_config_dir, 'thefuck'))
def _setup_user_dir(self): def _setup_user_dir(self):
"""Returns user config dir, create it when it doesn't exist.""" """Returns user config dir, create it when it doesn't exist."""
@@ -95,7 +98,7 @@ class Settings(dict):
elif attr in ('wait_command', 'history_limit', 'wait_slow_command'): elif attr in ('wait_command', 'history_limit', 'wait_slow_command'):
return int(val) return int(val)
elif attr in ('require_confirmation', 'no_colors', 'debug', elif attr in ('require_confirmation', 'no_colors', 'debug',
'alter_history'): 'alter_history', 'instant_mode'):
return val.lower() == 'true' return val.lower() == 'true'
elif attr == 'slow_commands': elif attr == 'slow_commands':
return val.split(':') return val.split(':')
@@ -108,5 +111,19 @@ class Settings(dict):
for env, attr in const.ENV_TO_ATTR.items() for env, attr in const.ENV_TO_ATTR.items()
if env in os.environ} 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) settings = Settings(const.DEFAULT_SETTINGS)

View File

@@ -33,7 +33,9 @@ DEFAULT_SETTINGS = {'rules': DEFAULT_RULES,
'alter_history': True, 'alter_history': True,
'wait_slow_command': 15, 'wait_slow_command': 15,
'slow_commands': ['lein', 'react-native', 'gradle', 'slow_commands': ['lein', 'react-native', 'gradle',
'./gradlew'], './gradlew', 'vagrant'],
'repeat': False,
'instant_mode': False,
'env': {'LC_ALL': 'C', 'LANG': 'C', 'GIT_TRACE': '1'}} 'env': {'LC_ALL': 'C', 'LANG': 'C', 'GIT_TRACE': '1'}}
ENV_TO_ATTR = {'THEFUCK_RULES': 'rules', ENV_TO_ATTR = {'THEFUCK_RULES': 'rules',
@@ -46,7 +48,9 @@ ENV_TO_ATTR = {'THEFUCK_RULES': 'rules',
'THEFUCK_HISTORY_LIMIT': 'history_limit', 'THEFUCK_HISTORY_LIMIT': 'history_limit',
'THEFUCK_ALTER_HISTORY': 'alter_history', 'THEFUCK_ALTER_HISTORY': 'alter_history',
'THEFUCK_WAIT_SLOW_COMMAND': 'wait_slow_command', '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 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

View File

@@ -1,9 +1,6 @@
try:
from pathlib import Path
except ImportError:
from pathlib2 import Path
from .conf import settings from .conf import settings
from .types import Rule from .types import Rule
from .system import Path
from . import logs from . import logs

View File

@@ -4,3 +4,7 @@ class EmptyCommand(Exception):
class NoRuleMatched(Exception): class NoRuleMatched(Exception):
"""Raised when no rule matched for some command.""" """Raised when no rule matched for some command."""
class ScriptNotInLog(Exception):
"""Script not found in log."""

View File

@@ -6,6 +6,7 @@ import sys
from traceback import format_exception from traceback import format_exception
import colorama import colorama
from .conf import settings from .conf import settings
from . import const
def color(color_): def color(color_):
@@ -16,6 +17,14 @@ def color(color_):
return 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): def exception(title, exc_info):
sys.stderr.write( sys.stderr.write(
u'{warn}[WARN] {title}:{reset}\n{trace}' u'{warn}[WARN] {title}:{reset}\n{trace}'
@@ -39,7 +48,8 @@ def failed(msg):
def show_corrected_command(corrected_command): 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, script=corrected_command.script,
side_effect=u' (+side effect)' if corrected_command.side_effect else u'', side_effect=u' (+side effect)' if corrected_command.side_effect else u'',
bold=color(colorama.Style.BRIGHT), bold=color(colorama.Style.BRIGHT),
@@ -48,9 +58,10 @@ def show_corrected_command(corrected_command):
def confirm_text(corrected_command): def confirm_text(corrected_command):
sys.stderr.write( 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'[{green}enter{reset}/{blue}{reset}/{blue}{reset}'
u'/{red}ctrl+c{reset}]').format( u'/{red}ctrl+c{reset}]').format(
prefix=const.USER_COMMAND_MARK,
script=corrected_command.script, script=corrected_command.script,
side_effect=' (+side effect)' if corrected_command.side_effect else '', side_effect=' (+side effect)' if corrected_command.side_effect else '',
clear='\033[1K\r', clear='\033[1K\r',
@@ -80,16 +91,50 @@ def debug_time(msg):
def how_to_configure_alias(configuration_details): def how_to_configure_alias(configuration_details):
print("Seems like {bold}fuck{reset} alias isn't configured!".format( print(u"Seems like {bold}fuck{reset} alias isn't configured!".format(
bold=color(colorama.Style.BRIGHT), bold=color(colorama.Style.BRIGHT),
reset=color(colorama.Style.RESET_ALL))) reset=color(colorama.Style.RESET_ALL)))
if configuration_details: if configuration_details:
content, path = configuration_details
print( print(
"Please put {bold}{content}{reset} in your " u"Please put {bold}{content}{reset} in your "
"{bold}{path}{reset}.".format( u"{bold}{path}{reset} and apply "
u"changes with {bold}{reload}{reset} or restart your shell.".format(
bold=color(colorama.Style.BRIGHT), bold=color(colorama.Style.BRIGHT),
reset=color(colorama.Style.RESET_ALL), reset=color(colorama.Style.RESET_ALL),
path=path, **configuration_details._asdict()))
content=content))
print('More details - https://github.com/nvbn/thefuck#manual-installation') if configuration_details.can_configure_automatically:
print(
u"Or run {bold}fuck{reset} second time for configuring"
u" it automatically.".format(
bold=color(colorama.Style.BRIGHT),
reset=color(colorama.Style.RESET_ALL)))
print(u'More details - https://github.com/nvbn/thefuck#manual-installation')
def already_configured(configuration_details):
print(
u"Seems like {bold}fuck{reset} alias already configured!\n"
u"For applying changes run {bold}{reload}{reset}"
u" or restart your shell.".format(
bold=color(colorama.Style.BRIGHT),
reset=color(colorama.Style.RESET_ALL),
reload=configuration_details.reload))
def configured_successfully(configuration_details):
print(
u"{bold}fuck{reset} alias configured successfully!\n"
u"For applying changes run {bold}{reload}{reset}"
u" or restart your shell.".format(
bold=color(colorama.Style.BRIGHT),
reset=color(colorama.Style.RESET_ALL),
reload=configuration_details.reload))
def version(thefuck_version, python_version):
sys.stderr.write(
u'The Fuck {} using Python {}\n'.format(thefuck_version,
python_version))

View File

@@ -3,27 +3,30 @@ from .system import init_output
init_output() init_output()
from argparse import ArgumentParser from pprint import pformat # noqa: E402
from warnings import warn import sys # noqa: E402
from pprint import pformat import six # noqa: E402
import sys from . import logs, types # noqa: E402
from . import logs, types from .shells import shell # noqa: E402
from .shells import shell from .conf import settings # noqa: E402
from .conf import settings from .corrector import get_corrected_commands # noqa: E402
from .corrector import get_corrected_commands from .exceptions import EmptyCommand # noqa: E402
from .exceptions import EmptyCommand from .ui import select_command # noqa: E402
from .utils import get_installation_info, get_alias from .argument_parser import Parser # noqa: E402
from .ui import select_command 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.""" """Fixes previous command. Used when `thefuck` called without arguments."""
settings.init() settings.init(known_args)
with logs.debug_time('Total'): with logs.debug_time('Total'):
logs.debug(u'Run with settings: {}'.format(pformat(settings))) 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: try:
command = types.Command.from_raw_script(sys.argv[1:]) command = types.Command.from_raw_script(raw_command)
except EmptyCommand: except EmptyCommand:
logs.debug('Empty command, nothing to do') logs.debug('Empty command, nothing to do')
return return
@@ -37,48 +40,31 @@ def fix_command():
sys.exit(1) sys.exit(1)
def print_alias(entry_point=True):
"""Prints alias for current shell."""
if entry_point:
warn('`thefuck-alias` is deprecated, use `thefuck --alias` instead.')
position = 1
else:
position = 2
alias = get_alias()
if len(sys.argv) > position:
alias = sys.argv[position]
print(shell.app_alias(alias))
def how_to_configure_alias():
"""Shows useful information about how-to configure alias.
It'll be only visible when user type fuck and when alias isn't configured.
"""
settings.init()
logs.how_to_configure_alias(shell.how_to_configure())
def main(): def main():
parser = ArgumentParser(prog='thefuck') parser = Parser()
version = get_installation_info().version known_args = parser.parse(sys.argv)
parser.add_argument(
'-v', '--version', if known_args.help:
action='version', parser.print_help()
version='The Fuck {} using Python {}'.format( elif known_args.version:
version, sys.version.split()[0])) logs.version(get_installation_info().version,
parser.add_argument('-a', '--alias', sys.version.split()[0])
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])
if known_args.alias:
print_alias(False)
elif known_args.command: elif known_args.command:
fix_command() fix_command(known_args)
elif known_args.alias:
if six.PY2:
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: else:
parser.print_usage() parser.print_usage()

111
thefuck/not_configured.py Normal file
View File

@@ -0,0 +1,111 @@
# Initialize output before importing any module, that can use colorama.
from .system import init_output
init_output()
import os # noqa: E402
import json # noqa: E402
import time # noqa: E402
import six # noqa: E402
from psutil import Process # noqa: E402
from . import logs, const # noqa: E402
from .shells import shell # noqa: E402
from .conf import settings # noqa: E402
from .system import Path # noqa: E402
from .utils import get_cache_dir # noqa: E402
def _get_shell_pid():
"""Returns parent process pid."""
proc = Process(os.getpid())
try:
return proc.parent().pid
except TypeError:
return proc.parent.pid
def _get_not_configured_usage_tracker_path():
"""Returns path of special file where we store latest shell pid."""
return Path(get_cache_dir()).joinpath('thefuck.last_not_configured_run')
def _record_first_run():
"""Records shell pid to tracker file."""
info = {'pid': _get_shell_pid(),
'time': time.time()}
mode = 'wb' if six.PY2 else 'w'
with _get_not_configured_usage_tracker_path().open(mode) as tracker:
json.dump(info, tracker)
def _get_previous_command():
history = shell.get_history()
if history:
return history[-1]
else:
return None
def _is_second_run():
"""Returns `True` when we know that `fuck` called second time."""
tracker_path = _get_not_configured_usage_tracker_path()
if not tracker_path.exists():
return False
current_pid = _get_shell_pid()
with tracker_path.open('r') as tracker:
try:
info = json.load(tracker)
except ValueError:
return False
if not (isinstance(info, dict) and info.get('pid') == current_pid):
return False
return (_get_previous_command() == 'fuck' or
time.time() - info.get('time', 0) < const.CONFIGURATION_TIMEOUT)
def _is_already_configured(configuration_details):
"""Returns `True` when alias already in shell config."""
path = Path(configuration_details.path).expanduser()
with path.open('r') as shell_config:
return configuration_details.content in shell_config.read()
def _configure(configuration_details):
"""Adds alias to shell config."""
path = Path(configuration_details.path).expanduser()
with path.open('a') as shell_config:
shell_config.write(u'\n')
shell_config.write(configuration_details.content)
shell_config.write(u'\n')
def main():
"""Shows useful information about how-to configure alias on a first run
and configure automatically on a second.
It'll be only visible when user type fuck and when alias isn't configured.
"""
settings.init()
configuration_details = shell.how_to_configure()
if (
configuration_details and
configuration_details.can_configure_automatically
):
if _is_already_configured(configuration_details):
logs.already_configured(configuration_details)
return
elif _is_second_run():
_configure(configuration_details)
logs.configured_successfully(configuration_details)
return
else:
_record_first_run()
logs.how_to_configure_alias(configuration_details)

View 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)

View 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

View 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

View File

@@ -0,0 +1,10 @@
from thefuck.utils import for_app
@for_app('ag')
def match(command):
return command.stderr.endswith('run ag with -Q\n')
def get_new_command(command):
return command.script.replace('ag', 'ag -Q', 1)

View File

@@ -29,7 +29,7 @@ def get_package(executable):
def match(command): def match(command):
if 'not found' in command.stderr: if 'not found' in command.stderr or 'not installed' in command.stderr:
executable = _get_executable(command) executable = _get_executable(command)
return not which(executable) and get_package(executable) return not which(executable) and get_package(executable)
else: else:

Some files were not shown because too many files have changed in this diff Show More