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:
parent
de5f85982f
commit
be467d5e50
1
TODO
1
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
|
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:
|
||||||
|
@ -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.
|
||||||
|
|
||||||
|
@ -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.
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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,
|
||||||
|
@ -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.
|
||||||
|
@ -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)
|
||||||
|
@ -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)
|
||||||
|
Loading…
Reference in New Issue
Block a user