From f9f094834926052034ecec1b2fe721dfd465cd56 Mon Sep 17 00:00:00 2001 From: nvbn Date: Fri, 24 Jul 2015 00:12:29 +0300 Subject: [PATCH] #N/A Add `docker_not_command` rule --- README.md | 1 + tests/rules/test_docker_not_command.py | 129 +++++++++++++++++++++++++ thefuck/rules/docker_not_command.py | 28 ++++++ 3 files changed, 158 insertions(+) create mode 100644 tests/rules/test_docker_not_command.py create mode 100644 thefuck/rules/docker_not_command.py diff --git a/README.md b/README.md index 4dfabb44..aa65af00 100644 --- a/README.md +++ b/README.md @@ -141,6 +141,7 @@ using the matched rule and runs it. Rules enabled by default are as follows: * `cpp11` – adds missing `-std=c++11` to `g++` or `clang++`; * `django_south_ghost` – adds `--delete-ghost-migrations` to failed because ghosts django south migration; * `django_south_merge` – adds `--merge` to inconsistent django south migration; +* `docker_not_command` – fixes wrong docker commands like `docker tags`; * `dry` – fixes repetitions like `git git push`; * `fix_alt_space` – replaces Alt+Space with Space character; * `git_add` – fixes *"Did you forget to 'git add'?"*; diff --git a/tests/rules/test_docker_not_command.py b/tests/rules/test_docker_not_command.py new file mode 100644 index 00000000..1e7b0061 --- /dev/null +++ b/tests/rules/test_docker_not_command.py @@ -0,0 +1,129 @@ +import pytest +from io import BytesIO +from tests.utils import Command +from thefuck.rules.docker_not_command import get_new_command, match + + +@pytest.fixture +def docker_help(mocker): + help = b'''Usage: docker [OPTIONS] COMMAND [arg...] + +A self-sufficient runtime for linux containers. + +Options: + + --api-cors-header= Set CORS headers in the remote API + -b, --bridge= Attach containers to a network bridge + --bip= Specify network bridge IP + -D, --debug=false Enable debug mode + -d, --daemon=false Enable daemon mode + --default-gateway= Container default gateway IPv4 address + --default-gateway-v6= Container default gateway IPv6 address + --default-ulimit=[] Set default ulimits for containers + --dns=[] DNS server to use + --dns-search=[] DNS search domains to use + -e, --exec-driver=native Exec driver to use + --exec-opt=[] Set exec driver options + --exec-root=/var/run/docker Root of the Docker execdriver + --fixed-cidr= IPv4 subnet for fixed IPs + --fixed-cidr-v6= IPv6 subnet for fixed IPs + -G, --group=docker Group for the unix socket + -g, --graph=/var/lib/docker Root of the Docker runtime + -H, --host=[] Daemon socket(s) to connect to + -h, --help=false Print usage + --icc=true Enable inter-container communication + --insecure-registry=[] Enable insecure registry communication + --ip=0.0.0.0 Default IP when binding container ports + --ip-forward=true Enable net.ipv4.ip_forward + --ip-masq=true Enable IP masquerading + --iptables=true Enable addition of iptables rules + --ipv6=false Enable IPv6 networking + -l, --log-level=info Set the logging level + --label=[] Set key=value labels to the daemon + --log-driver=json-file Default driver for container logs + --log-opt=map[] Set log driver options + --mtu=0 Set the containers network MTU + -p, --pidfile=/var/run/docker.pid Path to use for daemon PID file + --registry-mirror=[] Preferred Docker registry mirror + -s, --storage-driver= Storage driver to use + --selinux-enabled=false Enable selinux support + --storage-opt=[] Set storage driver options + --tls=false Use TLS; implied by --tlsverify + --tlscacert=~/.docker/ca.pem Trust certs signed only by this CA + --tlscert=~/.docker/cert.pem Path to TLS certificate file + --tlskey=~/.docker/key.pem Path to TLS key file + --tlsverify=false Use TLS and verify the remote + --userland-proxy=true Use userland proxy for loopback traffic + -v, --version=false Print version information and quit + +Commands: + attach Attach to a running container + build Build an image from a Dockerfile + commit Create a new image from a container's changes + cp Copy files/folders from a container's filesystem to the host path + create Create a new container + diff Inspect changes on a container's filesystem + events Get real time events from the server + exec Run a command in a running container + export Stream the contents of a container as a tar archive + history Show the history of an image + images List images + import Create a new filesystem image from the contents of a tarball + info Display system-wide information + inspect Return low-level information on a container or image + kill Kill a running container + load Load an image from a tar archive + login Register or log in to a Docker registry server + logout Log out from a Docker registry server + logs Fetch the logs of a container + pause Pause all processes within a container + port Lookup the public-facing port that is NAT-ed to PRIVATE_PORT + ps List containers + pull Pull an image or a repository from a Docker registry server + push Push an image or a repository to a Docker registry server + rename Rename an existing container + restart Restart a running container + rm Remove one or more containers + rmi Remove one or more images + run Run a command in a new container + save Save an image to a tar archive + search Search for an image on the Docker Hub + start Start a stopped container + stats Display a stream of a containers' resource usage statistics + stop Stop a running container + tag Tag an image into a repository + top Lookup the running processes of a container + unpause Unpause a paused container + version Show the Docker version information + wait Block until a container stops, then print its exit code + +Run 'docker COMMAND --help' for more information on a command. +''' + mock = mocker.patch('subprocess.Popen') + mock.return_value.stdout = BytesIO(help) + return mock + + +def stderr(cmd): + return "docker: '{}' is not a docker command.\n" \ + "See 'docker --help'.".format(cmd) + + +def test_match(): + assert match(Command('docker pes', stderr=stderr('pes')), None) + + +@pytest.mark.parametrize('script, stderr', [ + ('docker ps', ''), + ('cat pes', stderr('pes'))]) +def test_not_match(script, stderr): + assert not match(Command(script, stderr=stderr), None) + + +@pytest.mark.usefixtures('docker_help') +@pytest.mark.parametrize('wrong, fixed', [ + ('pes', 'ps'), + ('tags', 'tag')]) +def test_get_new_command(wrong, fixed): + command = Command('docker {}'.format(wrong), stderr=stderr(wrong)) + assert get_new_command(command, None) == 'docker {}'.format(fixed) diff --git a/thefuck/rules/docker_not_command.py b/thefuck/rules/docker_not_command.py new file mode 100644 index 00000000..24c828f0 --- /dev/null +++ b/thefuck/rules/docker_not_command.py @@ -0,0 +1,28 @@ +from itertools import dropwhile, takewhile, islice +import re +import subprocess +from thefuck.utils import get_closest, sudo_support + + +@sudo_support +def match(command, settings): + return command.script.startswith('docker') \ + and 'is not a docker command' in command.stderr + + +def get_docker_commands(): + proc = subprocess.Popen('docker', stdout=subprocess.PIPE) + lines = [line.decode('utf-8') for line in proc.stdout.readlines()] + lines = dropwhile(lambda line: not line.startswith('Commands:'), lines) + lines = islice(lines, 1, None) + lines = list(takewhile(lambda line: line != '\n', lines)) + return [line.strip().split(' ')[0] for line in lines] + + +@sudo_support +def get_new_command(command, settings): + wrong_command = re.findall( + r"docker: '(\w+)' is not a docker command.", command.stderr)[0] + fixed_command = get_closest(wrong_command, get_docker_commands()) + return command.script.replace( + ' {}'.format(wrong_command), ' {}'.format(fixed_command), 1)