Refactor command argument splitting.

This uses split=True/False as cmdutils.register argument again (because
we would never want to set maxsplit ourselves, only not splitting the
*last* argument makes any sense.

Also this uses shlex for splitting again, like earlier.
This commit is contained in:
Florian Bruhin 2014-05-02 10:20:08 +02:00
parent de5f85982f
commit be467d5e50
9 changed files with 50 additions and 25 deletions

1
TODO
View File

@ -40,7 +40,6 @@ Ctrl+A/X to increase/decrease last number in URL
command completion gets hidden when doing a new ValueList value
logging contexts
catch import errors for PyQt and QtWebKit
How do we handle empty values in input bar?
- Add more element-selection-detection code (with options?) based on:
-> javascript: http://stackoverflow.com/a/2848120/2085149
-> microFocusChanged and check active element via:

View File

@ -420,7 +420,7 @@ class QuteBrowser(QApplication):
logging.debug("maybe_quit quitting.")
self.quit()
@cmdutils.register(instance='', maxsplit=0)
@cmdutils.register(instance='', split=False)
def pyeval(self, s):
"""Evaluate a python string and display the results as a webpage.

View File

@ -86,7 +86,8 @@ class CurCommandDispatcher(QObject):
return
widget.hintmanager.follow_prevnext(frame, widget.url(), prev, newtab)
@cmdutils.register(instance='mainwindow.tabs.cur', name='open', maxsplit=0)
@cmdutils.register(instance='mainwindow.tabs.cur', name='open',
split=False)
def openurl(self, url, count=None):
"""Open an url in the current/[count]th tab.

View File

@ -32,10 +32,7 @@ class Command(QObject):
Attributes:
name: The main name of the command.
maxsplit: Maximum count of splits to be made.
-1: Split everything (default)
0: Don't split.
n: Split a maximum of n times.
split: Whether to split the arguments.
hide: Whether to hide the arguments or not.
nargs: A (minargs, maxargs) tuple, maxargs = None if there's no limit.
count: Whether the command supports a count, or not.
@ -57,13 +54,13 @@ class Command(QObject):
signal = pyqtSignal(tuple)
def __init__(self, name, maxsplit, hide, nargs, count, desc, instance,
def __init__(self, name, split, hide, nargs, count, desc, instance,
handler, completion, modes, not_modes, needs_js):
# I really don't know how to solve this in a better way, I tried.
# pylint: disable=too-many-arguments
super().__init__()
self.name = name
self.maxsplit = maxsplit
self.split = split
self.hide = hide
self.nargs = nargs
self.count = count

View File

@ -26,6 +26,7 @@ import qutebrowser.config.config as config
import qutebrowser.commands.utils as cmdutils
import qutebrowser.utils.message as message
from qutebrowser.commands._exceptions import NoSuchCommandError, CommandError
from qutebrowser.utils.misc import safe_shlex_split
def split_cmdline(text):
@ -168,15 +169,15 @@ class CommandManager:
if len(parts) == 1:
args = []
elif cmd.split:
args = safe_shlex_split(parts[1])
else:
logging.debug("Splitting '{}' with max {} splits".format(
parts[1], cmd.maxsplit))
args = parts[1].split(maxsplit=cmd.maxsplit)
args = parts[1].split(maxsplit=cmd.nargs[0] - 1)
self._cmd = cmd
self._args = args
retargs = args[:]
if text.endswith(' ') and (cmd.maxsplit == -1 or
len(args) <= cmd.maxsplit):
if text.endswith(' ') and (cmd.split is True or
len(args) < cmd.args[0]):
retargs.append('')
return [cmdstr] + retargs

View File

@ -40,10 +40,7 @@ class register: # pylint: disable=invalid-name
instance: The instance to be used as "self", as a dotted string.
name: The name (as string) or names (as list) of the command.
nargs: A (minargs, maxargs) tuple of valid argument counts, or an int.
maxsplit: Maximum count of splits to be made.
-1: Split everything (default)
0: Don't split.
n: Split a maximum of n times.
split: Whether to split the arguments.
hide: Whether to hide the command or not.
completion: Which completion to use for arguments, as a list of
strings.
@ -51,7 +48,7 @@ class register: # pylint: disable=invalid-name
needs_js: If javascript is needed for this command.
"""
def __init__(self, instance=None, name=None, nargs=None, maxsplit=-1,
def __init__(self, instance=None, name=None, nargs=None, split=True,
hide=False, completion=None, modes=None, not_modes=None,
needs_js=False):
"""Save decorator arguments.
@ -64,7 +61,7 @@ class register: # pylint: disable=invalid-name
if modes is not None and not_modes is not None:
raise ValueError("Only modes or not_modes can be given!")
self.name = name
self.maxsplit = maxsplit
self.split = split
self.hide = hide
self.nargs = nargs
self.instance = instance
@ -105,7 +102,7 @@ class register: # pylint: disable=invalid-name
desc = func.__doc__.splitlines()[0].strip().rstrip('.')
else:
desc = ""
cmd = Command(name=mainname, maxsplit=self.maxsplit,
cmd = Command(name=mainname, split=self.split,
hide=self.hide, nargs=nargs, count=count, desc=desc,
instance=self.instance, handler=func,
completion=self.completion, modes=self.modes,

View File

@ -310,7 +310,7 @@ class ConfigManager(QObject):
newval = val.typ.transform(newval)
return newval
@cmdutils.register(name='set', instance='config', maxsplit=2,
@cmdutils.register(name='set', instance='config',
completion=['section', 'option', 'value'])
def set_wrapper(self, section, option, value):
"""Set an option.
@ -322,7 +322,7 @@ class ConfigManager(QObject):
except (NoOptionError, NoSectionError, ValidationError) as e:
message.error("set: {} - {}".format(e.__class__.__name__, e))
@cmdutils.register(name='set_temp', instance='config', maxsplit=2,
@cmdutils.register(name='set_temp', instance='config',
completion=['section', 'option', 'value'])
def set_temp_wrapper(self, section, option, value):
"""Set a temporary option.

View File

@ -17,6 +17,7 @@
"""Other utilities which don't fit anywhere else."""
import shlex
from functools import reduce
from pkg_resources import resource_string
@ -46,3 +47,32 @@ def dotted_getattr(obj, path):
The object at path.
"""
return reduce(getattr, path.split('.'), obj)
def safe_shlex_split(s):
r"""Split a string via shlex safely (don't bail out on unbalanced quotes).
We split while the user is typing (for completion), and as
soon as " or \ is typed, the string is invalid for shlex,
because it encounters EOF while in quote/escape state.
Here we fix this error temporarely so shlex doesn't blow up,
and then retry splitting again.
Since shlex raises ValueError in both cases we unfortunately
have to parse the exception string...
"""
try:
return shlex.split(s)
except ValueError as e:
if str(e) == "No closing quotation":
# e.g. eggs "bacon ham
# -> we fix this as eggs "bacon ham"
s += '"'
elif str(e) == "No escaped character":
# e.g. eggs\
# -> we fix this as eggs\\
s += '\\'
else:
raise
return shlex.split(s)

View File

@ -222,12 +222,12 @@ class TabbedBrowser(TabWidget):
elif last_close == 'blank':
tab.openurl('about:blank')
@cmdutils.register(instance='mainwindow.tabs', maxsplit=0)
@cmdutils.register(instance='mainwindow.tabs', split=False)
def tabopen(self, url):
"""Open a new tab with a given url."""
self._tabopen(url, background=False)
@cmdutils.register(instance='mainwindow.tabs', maxsplit=0)
@cmdutils.register(instance='mainwindow.tabs', split=False)
def backtabopen(self, url):
"""Open a new tab in background."""
self._tabopen(url, background=True)