From be467d5e50e9229a0d9badb24ee2b6ab8fd3cd21 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Fri, 2 May 2014 10:20:08 +0200 Subject: [PATCH] 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. --- TODO | 1 - qutebrowser/app.py | 2 +- qutebrowser/browser/curcommand.py | 3 ++- qutebrowser/commands/_command.py | 9 +++----- qutebrowser/commands/managers.py | 11 +++++----- qutebrowser/commands/utils.py | 11 ++++------ qutebrowser/config/config.py | 4 ++-- qutebrowser/utils/misc.py | 30 +++++++++++++++++++++++++++ qutebrowser/widgets/_tabbedbrowser.py | 4 ++-- 9 files changed, 50 insertions(+), 25 deletions(-) diff --git a/TODO b/TODO index 7bb301e30..1e5b6d825 100644 --- a/TODO +++ b/TODO @@ -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: diff --git a/qutebrowser/app.py b/qutebrowser/app.py index df7eb7fc3..531004a53 100644 --- a/qutebrowser/app.py +++ b/qutebrowser/app.py @@ -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. diff --git a/qutebrowser/browser/curcommand.py b/qutebrowser/browser/curcommand.py index 7d22dba53..0e2193e68 100644 --- a/qutebrowser/browser/curcommand.py +++ b/qutebrowser/browser/curcommand.py @@ -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. diff --git a/qutebrowser/commands/_command.py b/qutebrowser/commands/_command.py index 678360f75..38cb95263 100644 --- a/qutebrowser/commands/_command.py +++ b/qutebrowser/commands/_command.py @@ -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 diff --git a/qutebrowser/commands/managers.py b/qutebrowser/commands/managers.py index ddb9836f4..ca8c7cec1 100644 --- a/qutebrowser/commands/managers.py +++ b/qutebrowser/commands/managers.py @@ -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 diff --git a/qutebrowser/commands/utils.py b/qutebrowser/commands/utils.py index db240f299..20e47bbbb 100644 --- a/qutebrowser/commands/utils.py +++ b/qutebrowser/commands/utils.py @@ -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, diff --git a/qutebrowser/config/config.py b/qutebrowser/config/config.py index c2649ac96..33c500a02 100644 --- a/qutebrowser/config/config.py +++ b/qutebrowser/config/config.py @@ -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. diff --git a/qutebrowser/utils/misc.py b/qutebrowser/utils/misc.py index 341a8cb1c..8affc9a5d 100644 --- a/qutebrowser/utils/misc.py +++ b/qutebrowser/utils/misc.py @@ -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) diff --git a/qutebrowser/widgets/_tabbedbrowser.py b/qutebrowser/widgets/_tabbedbrowser.py index 440444815..c80f3dbcb 100644 --- a/qutebrowser/widgets/_tabbedbrowser.py +++ b/qutebrowser/widgets/_tabbedbrowser.py @@ -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)