1
0
mirror of https://github.com/nvbn/thefuck.git synced 2025-02-21 20:38:54 +00:00

Merge branch 'fix-split' of https://github.com/mcarton/thefuck into mcarton-fix-split

This commit is contained in:
nvbn 2015-10-29 00:04:29 +08:00
commit a4c391096a
28 changed files with 167 additions and 103 deletions

View File

@ -1,7 +1,8 @@
import os 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
from tests.utils import Command from tests.utils import Command
@ -32,34 +33,40 @@ def tar_error(tmpdir):
return fixture return fixture
parametrize_filename = pytest.mark.parametrize('filename', [ parametrize_extensions = pytest.mark.parametrize('ext', tar_extensions)
'foo.tar',
'foo.tar.gz', # (filename as typed by the user, unquoted filename, quoted filename as per shells.quote)
'foo.tgz']) parametrize_filename = pytest.mark.parametrize('filename, unquoted, quoted', [
('foo{}', 'foo{}', 'foo{}'),
('foo\ bar{}', 'foo bar{}', "'foo bar{}'"),
('"foo bar{}"', 'foo bar{}', "'foo bar{}'")])
parametrize_script = pytest.mark.parametrize('script, fixed', [ parametrize_script = pytest.mark.parametrize('script, fixed', [
('tar xvf {}', 'mkdir -p foo && tar xvf {} -C foo'), ('tar xvf {}', 'mkdir -p {dir} && tar xvf {filename} -C {dir}'),
('tar -xvf {}', 'mkdir -p foo && tar -xvf {} -C foo'), ('tar -xvf {}', 'mkdir -p {dir} && tar -xvf {filename} -C {dir}'),
('tar --extract -f {}', 'mkdir -p foo && tar --extract -f {} -C foo')]) ('tar --extract -f {}', 'mkdir -p {dir} && tar --extract -f {filename} -C {dir}')])
@parametrize_extensions
@parametrize_filename @parametrize_filename
@parametrize_script @parametrize_script
def test_match(tar_error, filename, script, fixed): def test_match(ext, tar_error, filename, unquoted, quoted, script, fixed):
tar_error(filename) tar_error(unquoted.format(ext))
assert match(Command(script=script.format(filename))) assert match(Command(script=script.format(filename.format(ext))))
@parametrize_extensions
@parametrize_filename @parametrize_filename
@parametrize_script @parametrize_script
def test_side_effect(tar_error, filename, script, fixed): def test_side_effect(ext, tar_error, filename, unquoted, quoted, script, fixed):
tar_error(filename) tar_error(unquoted.format(ext))
side_effect(Command(script=script.format(filename)), None) side_effect(Command(script=script.format(filename.format(ext))), None)
assert set(os.listdir('.')) == {filename, 'd'} assert set(os.listdir('.')) == {unquoted.format(ext), 'd'}
@parametrize_extensions
@parametrize_filename @parametrize_filename
@parametrize_script @parametrize_script
def test_get_new_command(tar_error, filename, script, fixed): def test_get_new_command(ext, tar_error, filename, unquoted, quoted, script, fixed):
tar_error(filename) tar_error(unquoted.format(ext))
assert get_new_command(Command(script=script.format(filename))) == fixed.format(filename) assert (get_new_command(Command(script=script.format(filename.format(ext))))
== fixed.format(dir=quoted.format(''), filename=filename.format(ext)))

View File

@ -43,6 +43,8 @@ def test_side_effect(zip_error, script):
@pytest.mark.parametrize('script,fixed', [ @pytest.mark.parametrize('script,fixed', [
('unzip foo', 'unzip foo -d foo'), ('unzip foo', 'unzip foo -d foo'),
(R"unzip foo\ bar.zip", R"unzip foo\ bar.zip -d 'foo bar'"),
(R"unzip 'foo bar.zip'", R"unzip 'foo bar.zip' -d 'foo bar'"),
('unzip foo.zip', 'unzip foo.zip -d foo')]) ('unzip foo.zip', 'unzip foo.zip -d foo')])
def test_get_new_command(zip_error, script, fixed): def test_get_new_command(zip_error, script, fixed):
assert get_new_command(Command(script=script)) == fixed assert get_new_command(Command(script=script)) == fixed

View File

@ -1,17 +1,18 @@
from mock import Mock, patch from mock import patch
from thefuck.rules.has_exists_script import match, get_new_command from thefuck.rules.has_exists_script import match, get_new_command
from ..utils import Command
def test_match(): def test_match():
with patch('os.path.exists', return_value=True): with patch('os.path.exists', return_value=True):
assert match(Mock(script='main', stderr='main: command not found')) assert match(Command(script='main', stderr='main: command not found'))
assert match(Mock(script='main --help', assert match(Command(script='main --help',
stderr='main: command not found')) stderr='main: command not found'))
assert not match(Mock(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):
assert not match(Mock(script='main', stderr='main: command not found')) assert not match(Command(script='main', stderr='main: command not found'))
def test_get_new_command(): def test_get_new_command():
assert get_new_command(Mock(script='main --help')) == './main --help' assert get_new_command(Command(script='main --help')) == './main --help'

View File

@ -2,14 +2,14 @@ import re
from thefuck.utils import replace_argument, for_app from thefuck.utils import replace_argument, for_app
@for_app('cargo') @for_app('cargo', at_least=1)
def match(command): def match(command):
return ('No such subcommand' in command.stderr return ('No such subcommand' in command.stderr
and 'Did you mean' in command.stderr) and 'Did you mean' in command.stderr)
def get_new_command(command): def get_new_command(command):
broken = command.script.split()[1] broken = command.split_script[1]
fix = re.findall(r'Did you mean `([^`]*)`', command.stderr)[0] fix = re.findall(r'Did you mean `([^`]*)`', command.stderr)[0]
return replace_argument(command.script, broken, fix) return replace_argument(command.script, broken, fix)

View File

@ -33,7 +33,7 @@ def get_new_command(command):
defaults to the rules of cd_mkdir. defaults to the rules of cd_mkdir.
Change sensitivity by changing MAX_ALLOWED_DIFF. Default value is 0.6 Change sensitivity by changing MAX_ALLOWED_DIFF. Default value is 0.6
""" """
dest = command.script.split()[1].split(os.sep) dest = command.split_script[1].split(os.sep)
if dest[-1] == '': if dest[-1] == '':
dest = dest[:-1] dest = dest[:-1]
cwd = os.getcwd() cwd = os.getcwd()

View File

@ -1,7 +1,7 @@
from thefuck.utils import for_app from thefuck.utils import for_app
@for_app(['g++', 'clang++']) @for_app('g++', 'clang++')
def match(command): def match(command):
return ('This file requires compiler and library support for the ' return ('This file requires compiler and library support for the '
'ISO C++ 2011 standard.' in command.stderr or 'ISO C++ 2011 standard.' in command.stderr or

View File

@ -4,6 +4,11 @@ from thefuck import shells
from thefuck.utils import for_app from thefuck.utils import for_app
tar_extensions = ('.tar', '.tar.Z', '.tar.bz2', '.tar.gz', '.tar.lz',
'.tar.lzma', '.tar.xz', '.taz', '.tb2', '.tbz', '.tbz2',
'.tgz', '.tlz', '.txz', '.tz')
def _is_tar_extract(cmd): def _is_tar_extract(cmd):
if '--extract' in cmd: if '--extract' in cmd:
return True return True
@ -14,11 +19,8 @@ def _is_tar_extract(cmd):
def _tar_file(cmd): def _tar_file(cmd):
tar_extensions = ('.tar', '.tar.Z', '.tar.bz2', '.tar.gz', '.tar.lz',
'.tar.lzma', '.tar.xz', '.taz', '.tb2', '.tbz', '.tbz2',
'.tgz', '.tlz', '.txz', '.tz')
for c in cmd.split(): for c in cmd:
for ext in tar_extensions: for ext in tar_extensions:
if c.endswith(ext): if c.endswith(ext):
return (c, c[0:len(c) - len(ext)]) return (c, c[0:len(c) - len(ext)])
@ -28,16 +30,17 @@ def _tar_file(cmd):
def match(command): def match(command):
return ('-C' not in command.script return ('-C' not in command.script
and _is_tar_extract(command.script) and _is_tar_extract(command.script)
and _tar_file(command.script) is not None) and _tar_file(command.split_script) is not None)
def get_new_command(command): def get_new_command(command):
dir = shells.quote(_tar_file(command.split_script)[1])
return shells.and_('mkdir -p {dir}', '{cmd} -C {dir}') \ return shells.and_('mkdir -p {dir}', '{cmd} -C {dir}') \
.format(dir=_tar_file(command.script)[1], cmd=command.script) .format(dir=dir, cmd=command.script)
def side_effect(old_cmd, command): def side_effect(old_cmd, command):
with tarfile.TarFile(_tar_file(old_cmd.script)[0]) as archive: with tarfile.TarFile(_tar_file(old_cmd.split_script)[0]) as archive:
for file in archive.getnames(): for file in archive.getnames():
try: try:
os.remove(file) os.remove(file)

View File

@ -1,6 +1,7 @@
import os import os
import zipfile import zipfile
from thefuck.utils import for_app from thefuck.utils import for_app
from thefuck.shells import quote
def _is_bad_zip(file): def _is_bad_zip(file):
@ -13,7 +14,7 @@ def _zip_file(command):
# unzip [-flags] file[.zip] [file(s) ...] [-x file(s) ...] # unzip [-flags] file[.zip] [file(s) ...] [-x file(s) ...]
# ^ ^ files to unzip from the archive # ^ ^ files to unzip from the archive
# archive to unzip # archive to unzip
for c in command.script.split()[1:]: for c in command.split_script[1:]:
if not c.startswith('-'): if not c.startswith('-'):
if c.endswith('.zip'): if c.endswith('.zip'):
return c return c
@ -28,7 +29,7 @@ def match(command):
def get_new_command(command): def get_new_command(command):
return '{} -d {}'.format(command.script, _zip_file(command)[:-4]) return '{} -d {}'.format(command.script, quote(_zip_file(command)[:-4]))
def side_effect(old_cmd, command): def side_effect(old_cmd, command):

View File

@ -1,11 +1,13 @@
def match(command): def match(command):
split_command = command.script.split() split_command = command.split_script
return len(split_command) >= 2 and split_command[0] == split_command[1] return (split_command
and len(split_command) >= 2
and split_command[0] == split_command[1])
def get_new_command(command): def get_new_command(command):
return command.script[command.script.find(' ')+1:] return ' '.join(command.split_script[1:])
# it should be rare enough to actually have to type twice the same word, so # it should be rare enough to actually have to type twice the same word, so
# this rule can have a higher priority to come before things like "cd cd foo" # this rule can have a higher priority to come before things like "cd cd foo"

View File

@ -5,7 +5,8 @@ from thefuck.specific.git import git_support
@git_support @git_support
def match(command): def match(command):
# catches "git branch list" in place of "git branch" # catches "git branch list" in place of "git branch"
return command.script.split()[1:] == 'branch list'.split() return (command.split_script
and command.split_script[1:] == 'branch list'.split())
@git_support @git_support

View File

@ -5,9 +5,8 @@ from thefuck.specific.git import git_support
@git_support @git_support
def match(command): def match(command):
splited_script = command.script.split() if command.split_script and len(command.split_script) > 1:
if len(splited_script) > 1: return (command.split_script[1] == 'stash'
return (splited_script[1] == 'stash'
and 'usage:' in command.stderr) and 'usage:' in command.stderr)
else: else:
return False return False
@ -26,12 +25,12 @@ stash_commands = (
@git_support @git_support
def get_new_command(command): def get_new_command(command):
stash_cmd = command.script.split()[2] stash_cmd = command.split_script[2]
fixed = utils.get_closest(stash_cmd, stash_commands, fallback_to_first=False) fixed = utils.get_closest(stash_cmd, stash_commands, fallback_to_first=False)
if fixed is not None: if fixed is not None:
return replace_argument(command.script, stash_cmd, fixed) return replace_argument(command.script, stash_cmd, fixed)
else: else:
cmd = command.script.split() cmd = command.split_script[:]
cmd.insert(2, 'save') cmd.insert(2, 'save')
return ' '.join(cmd) return ' '.join(cmd)

View File

@ -4,7 +4,7 @@ from thefuck.specific.sudo import sudo_support
@sudo_support @sudo_support
def match(command): def match(command):
return os.path.exists(command.script.split()[0]) \ return command.split_script and os.path.exists(command.split_script[0]) \
and 'command not found' in command.stderr and 'command not found' in command.stderr

View File

@ -3,10 +3,10 @@ from thefuck.utils import for_app
@for_app('ls') @for_app('ls')
def match(command): def match(command):
return 'ls -' not in command.script return command.split_script and 'ls -' not in command.script
def get_new_command(command): def get_new_command(command):
command = command.script.split(' ') command = command.split_script[:]
command[0] = 'ls -lah' command[0] = 'ls -lah'
return ' '.join(command) return ' '.join(command)

View File

@ -1,5 +1,9 @@
from thefuck.utils import for_app
@for_app('man', at_least=1)
def match(command): def match(command):
return command.script.strip().startswith('man ') return True
def get_new_command(command): def get_new_command(command):
@ -8,7 +12,7 @@ def get_new_command(command):
if '2' in command.script: if '2' in command.script:
return command.script.replace("2", "3") return command.script.replace("2", "3")
split_cmd2 = command.script.split() split_cmd2 = command.split_script
split_cmd3 = split_cmd2[:] split_cmd3 = split_cmd2[:]
split_cmd2.insert(1, ' 2 ') split_cmd2.insert(1, ' 2 ')

View File

@ -21,7 +21,7 @@ def match(command):
def get_new_command(command): def get_new_command(command):
script = command.script.split(' ') script = command.split_script[:]
possibilities = extract_possibilities(command) possibilities = extract_possibilities(command)
script[1] = get_closest(script[1], possibilities) script[1] = get_closest(script[1], possibilities)
return ' '.join(script) return ' '.join(script)

View File

@ -5,16 +5,17 @@ from thefuck.specific.sudo import sudo_support
@sudo_support @sudo_support
def match(command): def match(command):
return 'not found' in command.stderr and \ return (command.split_script
bool(get_close_matches(command.script.split(' ')[0], and 'not found' in command.stderr
get_all_executables())) and bool(get_close_matches(command.split_script[0],
get_all_executables())))
@sudo_support @sudo_support
def get_new_command(command): def get_new_command(command):
old_command = command.script.split(' ')[0] old_command = command.split_script[0]
new_cmds = get_close_matches(old_command, get_all_executables(), cutoff=0.1) new_cmds = get_close_matches(old_command, get_all_executables(), cutoff=0.1)
return [' '.join([new_command] + command.script.split(' ')[1:]) return [' '.join([new_command] + command.split_script[1:])
for new_command in new_cmds] for new_command in new_cmds]

View File

@ -11,12 +11,14 @@ from thefuck.specific.archlinux import get_pkgfile, archlinux_env
def match(command): def match(command):
return (command.script.startswith(('pacman', 'sudo pacman', 'yaourt')) return (command.split_script
and (command.split_script[0] in ('pacman', 'yaourt')
or command.split_script[0:2] == ['sudo', 'pacman'])
and 'error: target not found:' in command.stderr) and 'error: target not found:' in command.stderr)
def get_new_command(command): def get_new_command(command):
pgr = command.script.split()[-1] pgr = command.split_script[-1]
return replace_command(command, pgr, get_pkgfile(pgr)) return replace_command(command, pgr, get_pkgfile(pgr))

View File

@ -6,8 +6,8 @@ from thefuck.specific.sudo import sudo_support
@sudo_support @sudo_support
def match(command): def match(command):
toks = command.script.split() toks = command.split_script
return (len(toks) > 0 return (toks
and toks[0].endswith('.py') and toks[0].endswith('.py')
and ('Permission denied' in command.stderr or and ('Permission denied' in command.stderr or
'command not found' in command.stderr)) 'command not found' in command.stderr))

View File

@ -5,7 +5,8 @@ enabled_by_default = False
@sudo_support @sudo_support
def match(command): def match(command):
return ({'rm', '/'}.issubset(command.script.split()) return (command.split_script
and {'rm', '/'}.issubset(command.split_script)
and '--no-preserve-root' not in command.script and '--no-preserve-root' not in command.script
and '--no-preserve-root' in command.stderr) and '--no-preserve-root' in command.stderr)

View File

@ -1,5 +1,6 @@
import shlex import shlex
from thefuck.utils import quote, for_app from thefuck.shells import quote
from thefuck.utils import for_app
@for_app('sed') @for_app('sed')

View File

@ -11,9 +11,11 @@ source_layouts = [u'''йцукенгшщзхъфывапролджэячсмит
@memoize @memoize
def _get_matched_layout(command): def _get_matched_layout(command):
# don't use command.split_script here because a layout mismatch will likely
# result in a non-splitable sript as per shlex
cmd = command.script.split(' ')
for source_layout in source_layouts: for source_layout in source_layouts:
if all([ch in source_layout or ch in '-_' if all([ch in source_layout or ch in '-_' for ch in cmd[0]]):
for ch in command.script.split(' ')[0]]):
return source_layout return source_layout

View File

@ -8,15 +8,15 @@ from thefuck.utils import for_app
@sudo_support @sudo_support
@for_app('systemctl') @for_app('systemctl')
def match(command): def match(command):
# Catches 'Unknown operation 'service'.' when executing systemctl with # Catches "Unknown operation 'service'." when executing systemctl with
# misordered arguments # misordered arguments
cmd = command.script.split() cmd = command.split_script
return ('Unknown operation \'' in command.stderr and return (cmd and 'Unknown operation \'' in command.stderr and
len(cmd) - cmd.index('systemctl') == 3) len(cmd) - cmd.index('systemctl') == 3)
@sudo_support @sudo_support
def get_new_command(command): def get_new_command(command):
cmd = command.script.split() cmd = command.split_script
cmd[-1], cmd[-2] = cmd[-2], cmd[-1] cmd[-1], cmd[-2] = cmd[-2], cmd[-1]
return ' '.join(cmd) return ' '.join(cmd)

View File

@ -8,13 +8,13 @@ def match(command):
def get_new_command(command): def get_new_command(command):
cmds = command.script.split(' ') cmds = command.split_script
machine = None machine = None
if len(cmds) >= 3: if len(cmds) >= 3:
machine = cmds[2] machine = cmds[2]
startAllInstances = shells.and_("vagrant up", command.script) startAllInstances = shells.and_("vagrant up", command.script)
if machine is None: if machine is None:
return startAllInstances return startAllInstances
else: else:
return [ shells.and_("vagrant up " + machine, command.script), startAllInstances] return [ shells.and_("vagrant up " + machine, command.script), startAllInstances]

View File

@ -1,7 +1,9 @@
# -*- encoding: utf-8 -*- # -*- encoding: utf-8 -*-
from six.moves.urllib.parse import urlparse from six.moves.urllib.parse import urlparse
from thefuck.utils import for_app
@for_app('whois', at_least=1)
def match(command): def match(command):
""" """
What the `whois` command returns depends on the 'Whois server' it contacted What the `whois` command returns depends on the 'Whois server' it contacted
@ -19,11 +21,11 @@ def match(command):
- www.google.fr subdomain: www, domain: 'google.fr'; - www.google.fr subdomain: www, domain: 'google.fr';
- google.co.uk subdomain: None, domain; 'google.co.uk'. - google.co.uk subdomain: None, domain; 'google.co.uk'.
""" """
return 'whois ' in command.script.strip() return True
def get_new_command(command): def get_new_command(command):
url = command.script.split()[1] url = command.split_script[1]
if '/' in command.script: if '/' in command.script:
return 'whois ' + urlparse(url).netloc return 'whois ' + urlparse(url).netloc

View File

@ -1,6 +1,6 @@
"""Module with shell specific actions, each shell class should """Module with shell specific actions, each shell class should
implement `from_shell`, `to_shell`, `app_alias`, `put_to_history` and `get_aliases` implement `from_shell`, `to_shell`, `app_alias`, `put_to_history` and
methods. `get_aliases` methods.
""" """
from collections import defaultdict from collections import defaultdict
@ -9,6 +9,8 @@ from subprocess import Popen, PIPE
from time import time from time import time
import io import io
import os import os
import shlex
import six
from .utils import DEVNULL, memoize, cache from .utils import DEVNULL, memoize, cache
@ -75,6 +77,20 @@ class Generic(object):
def how_to_configure(self): def how_to_configure(self):
return return
def split_command(self, command):
"""Split the command using shell-like syntax."""
return shlex.split(command)
def quote(self, s):
"""Return a shell-escaped version of the string s."""
if six.PY2:
from pipes import quote
else:
from shlex import quote
return quote(s)
class Bash(Generic): class Bash(Generic):
def app_alias(self, fuck): def app_alias(self, fuck):
@ -285,9 +301,18 @@ def get_aliases():
return list(_get_shell().get_aliases().keys()) return list(_get_shell().get_aliases().keys())
def split_command(command):
return _get_shell().split_command(command)
def quote(s):
return _get_shell().quote(s)
@memoize @memoize
def get_history(): def get_history():
return list(_get_shell().get_history()) return list(_get_shell().get_history())
def how_to_configure(): def how_to_configure():
return _get_shell().how_to_configure() return _get_shell().how_to_configure()

View File

@ -1,8 +1,7 @@
import re import re
from shlex import split
from decorator import decorator from decorator import decorator
from ..types import Command from ..utils import is_app
from ..utils import quote, is_app from ..shells import quote, split_command
@decorator @decorator
@ -24,7 +23,7 @@ def git_support(fn, command):
# 'commit' '--amend' # 'commit' '--amend'
# which is surprising and does not allow to easily test for # which is surprising and does not allow to easily test for
# eg. 'git commit' # eg. 'git commit'
expansion = ' '.join(map(quote, split(search.group(2)))) expansion = ' '.join(map(quote, split_command(search.group(2))))
new_script = command.script.replace(alias, expansion) new_script = command.script.replace(alias, expansion)
command = command.update(script=new_script) command = command.update(script=new_script)

View File

@ -1,13 +1,13 @@
from imp import load_source
import os
from subprocess import Popen, PIPE
import sys
from psutil import Process, TimeoutExpired
import six
from .conf import settings, DEFAULT_PRIORITY, ALL_ENABLED
from .utils import compatibility_call
from .exceptions import EmptyCommand
from . import logs, shells from . import logs, shells
from .conf import settings, DEFAULT_PRIORITY, ALL_ENABLED
from .exceptions import EmptyCommand
from .utils import compatibility_call
from imp import load_source
from psutil import Process, TimeoutExpired
from subprocess import Popen, PIPE
import os
import six
import sys
class Command(object): class Command(object):
@ -21,10 +21,23 @@ class Command(object):
:type stderr: basestring :type stderr: basestring
""" """
self.script = script self._script = script
self.stdout = stdout self.stdout = stdout
self.stderr = stderr self.stderr = stderr
try:
self._split_script = shells.split_command(script)
except:
self._split_script = None
@property
def script(self):
return self._script
@property
def split_script(self):
return self._split_script
def __eq__(self, other): def __eq__(self, other):
if isinstance(other, Command): if isinstance(other, Command):
return (self.script, self.stdout, self.stderr) \ return (self.script, self.stdout, self.stderr) \

View File

@ -12,16 +12,10 @@ from inspect import getargspec
from pathlib import Path from pathlib import Path
import pkg_resources import pkg_resources
import six
from .conf import settings from .conf import settings
DEVNULL = open(os.devnull, 'w') DEVNULL = open(os.devnull, 'w')
if six.PY2:
from pipes import quote
else:
from shlex import quote
def memoize(fn): def memoize(fn):
"""Caches previous calls to the function.""" """Caches previous calls to the function."""
@ -144,19 +138,23 @@ def replace_command(command, broken, matched):
@memoize @memoize
def is_app(command, *app_names): def is_app(command, *app_names, **kwargs):
"""Returns `True` if command is call to one of passed app names.""" """Returns `True` if command is call to one of passed app names."""
for name in app_names:
if command.script == name \ at_least = kwargs.pop('at_least', 0)
or command.script.startswith(u'{} '.format(name)): if kwargs:
return True raise TypeError("got an unexpected keyword argument '{}'".format(kwargs.keys()))
if command.split_script is not None and len(command.split_script) > at_least:
return command.split_script[0] in app_names
return False return False
def for_app(*app_names): def for_app(*app_names, **kwargs):
"""Specifies that matching script is for on of app names.""" """Specifies that matching script is for on of app names."""
def _for_app(fn, command): def _for_app(fn, command):
if is_app(command, *app_names): if is_app(command, *app_names, **kwargs):
return fn(command) return fn(command)
else: else:
return False return False