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 command completion gets hidden when doing a new ValueList value
logging contexts logging contexts
catch import errors for PyQt and QtWebKit 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: - Add more element-selection-detection code (with options?) based on:
-> javascript: http://stackoverflow.com/a/2848120/2085149 -> javascript: http://stackoverflow.com/a/2848120/2085149
-> microFocusChanged and check active element via: -> microFocusChanged and check active element via:

View File

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

View File

@ -86,7 +86,8 @@ class CurCommandDispatcher(QObject):
return return
widget.hintmanager.follow_prevnext(frame, widget.url(), prev, newtab) 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): def openurl(self, url, count=None):
"""Open an url in the current/[count]th tab. """Open an url in the current/[count]th tab.

View File

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

View File

@ -26,6 +26,7 @@ import qutebrowser.config.config as config
import qutebrowser.commands.utils as cmdutils import qutebrowser.commands.utils as cmdutils
import qutebrowser.utils.message as message import qutebrowser.utils.message as message
from qutebrowser.commands._exceptions import NoSuchCommandError, CommandError from qutebrowser.commands._exceptions import NoSuchCommandError, CommandError
from qutebrowser.utils.misc import safe_shlex_split
def split_cmdline(text): def split_cmdline(text):
@ -168,15 +169,15 @@ class CommandManager:
if len(parts) == 1: if len(parts) == 1:
args = [] args = []
elif cmd.split:
args = safe_shlex_split(parts[1])
else: else:
logging.debug("Splitting '{}' with max {} splits".format( args = parts[1].split(maxsplit=cmd.nargs[0] - 1)
parts[1], cmd.maxsplit))
args = parts[1].split(maxsplit=cmd.maxsplit)
self._cmd = cmd self._cmd = cmd
self._args = args self._args = args
retargs = args[:] retargs = args[:]
if text.endswith(' ') and (cmd.maxsplit == -1 or if text.endswith(' ') and (cmd.split is True or
len(args) <= cmd.maxsplit): len(args) < cmd.args[0]):
retargs.append('') retargs.append('')
return [cmdstr] + retargs 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. instance: The instance to be used as "self", as a dotted string.
name: The name (as string) or names (as list) of the command. name: The name (as string) or names (as list) of the command.
nargs: A (minargs, maxargs) tuple of valid argument counts, or an int. nargs: A (minargs, maxargs) tuple of valid argument counts, or an int.
maxsplit: Maximum count of splits to be made. split: Whether to split the arguments.
-1: Split everything (default)
0: Don't split.
n: Split a maximum of n times.
hide: Whether to hide the command or not. hide: Whether to hide the command or not.
completion: Which completion to use for arguments, as a list of completion: Which completion to use for arguments, as a list of
strings. strings.
@ -51,7 +48,7 @@ class register: # pylint: disable=invalid-name
needs_js: If javascript is needed for this command. 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, hide=False, completion=None, modes=None, not_modes=None,
needs_js=False): needs_js=False):
"""Save decorator arguments. """Save decorator arguments.
@ -64,7 +61,7 @@ class register: # pylint: disable=invalid-name
if modes is not None and not_modes is not None: if modes is not None and not_modes is not None:
raise ValueError("Only modes or not_modes can be given!") raise ValueError("Only modes or not_modes can be given!")
self.name = name self.name = name
self.maxsplit = maxsplit self.split = split
self.hide = hide self.hide = hide
self.nargs = nargs self.nargs = nargs
self.instance = instance self.instance = instance
@ -105,7 +102,7 @@ class register: # pylint: disable=invalid-name
desc = func.__doc__.splitlines()[0].strip().rstrip('.') desc = func.__doc__.splitlines()[0].strip().rstrip('.')
else: else:
desc = "" desc = ""
cmd = Command(name=mainname, maxsplit=self.maxsplit, cmd = Command(name=mainname, split=self.split,
hide=self.hide, nargs=nargs, count=count, desc=desc, hide=self.hide, nargs=nargs, count=count, desc=desc,
instance=self.instance, handler=func, instance=self.instance, handler=func,
completion=self.completion, modes=self.modes, completion=self.completion, modes=self.modes,

View File

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

View File

@ -17,6 +17,7 @@
"""Other utilities which don't fit anywhere else.""" """Other utilities which don't fit anywhere else."""
import shlex
from functools import reduce from functools import reduce
from pkg_resources import resource_string from pkg_resources import resource_string
@ -46,3 +47,32 @@ def dotted_getattr(obj, path):
The object at path. The object at path.
""" """
return reduce(getattr, path.split('.'), obj) 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': elif last_close == 'blank':
tab.openurl('about:blank') tab.openurl('about:blank')
@cmdutils.register(instance='mainwindow.tabs', maxsplit=0) @cmdutils.register(instance='mainwindow.tabs', split=False)
def tabopen(self, url): def tabopen(self, url):
"""Open a new tab with a given url.""" """Open a new tab with a given url."""
self._tabopen(url, background=False) self._tabopen(url, background=False)
@cmdutils.register(instance='mainwindow.tabs', maxsplit=0) @cmdutils.register(instance='mainwindow.tabs', split=False)
def backtabopen(self, url): def backtabopen(self, url):
"""Open a new tab in background.""" """Open a new tab in background."""
self._tabopen(url, background=True) self._tabopen(url, background=True)