Start using (broken) decorators

This commit is contained in:
Florian Bruhin 2014-03-03 06:09:23 +01:00
parent 2df9b57981
commit b22b19d881
7 changed files with 127 additions and 433 deletions

View File

@ -180,13 +180,7 @@ class QuteBrowser(QApplication):
Registers all commands, connects its signals, and sets up keyparser.
"""
cmdutils.register_all()
for cmd in cmdutils.cmd_dict.values():
cmd.signal.connect(self.cmd_handler)
try:
self.keyparser.from_config_sect(config.config['keybind'])
except KeyError:
pass
self.keyparser.from_config_sect(config.config['keybind'])
def _process_init_args(self):
"""Process initial positional args.
@ -328,67 +322,7 @@ class QuteBrowser(QApplication):
logging.debug("maybe_quit quitting.")
self.quit()
@pyqtSlot(tuple)
def cmd_handler(self, tpl):
"""Handle commands and delegate the specific actions.
This gets called as a slot from all commands, and then calls the
appropriate command handler.
All handlers supporting a count should have a keyword argument count.
Args:
tpl: A tuple in the form (count, argv) where argv is
[cmd, arg, ...]
Return:
The handlers return value.
"""
(count, argv) = tpl
cmd = argv[0]
args = argv[1:]
browser = self.mainwindow.tabs
handlers = {
'open': browser.cur.openurl,
'opencur': browser.opencur,
'tabopen': browser.tabopen,
'tabopencur': browser.tabopencur,
'quit': self.shutdown,
'tabclose': browser.tabclose,
'tabprev': browser.switch_prev,
'tabnext': browser.switch_next,
'reload': browser.cur.reloadpage,
'stop': browser.cur.stop,
'back': browser.cur.back,
'forward': browser.cur.forward,
'print': browser.cur.printpage,
'scroll': browser.cur.scroll,
'scroll_page': browser.cur.scroll_page,
'scroll_perc_x': browser.cur.scroll_percent_x,
'scroll_perc_y': browser.cur.scroll_percent_y,
'undo': browser.undo_close,
'pyeval': self.pyeval,
'settrace': set_trace,
'nextsearch': self.searchparser.nextsearch,
'yank': browser.cur.yank,
'yanktitle': browser.cur.yank_title,
'paste': browser.paste,
'tabpaste': browser.tabpaste,
'crash': self.crash,
'version': lambda: browser.cur.openurl('qute:version'),
'zoomin': browser.cur.zoom_in,
'zoomout': browser.cur.zoom_out,
}
handler = handlers[cmd]
if count is not None and self.sender().count:
return handler(*args, count=count)
else:
return handler(*args)
@cmdutils.register(split_args=False)
def pyeval(self, s):
"""Evaluate a python string and display the results as a webpage.
@ -406,6 +340,7 @@ class QuteBrowser(QApplication):
qutescheme.pyeval_output = out
self.mainwindow.tabs.cur.openurl('qute:pyeval')
@cmdutils.register(hide=True)
def crash(self):
"""Crash for debugging purposes.
@ -418,6 +353,7 @@ class QuteBrowser(QApplication):
raise Exception("Forced crash")
@pyqtSlot()
@cmdutils.register(name=['q', 'quit'], nargs=0)
def shutdown(self, do_quit=True):
"""Try to shutdown everything cleanly.

View File

@ -30,290 +30,3 @@ A command class can set the following properties:
hide -- If the command should be hidden in tab completion. Default: False
"""
from qutebrowser.commands.template import Command
class Open(Command):
"""Open a page in the current or [count]th tab.
arg: The URL to open.
"""
nargs = 1
split_args = False
count = True
class OpenCur(Command):
"""Fill the statusbar with :open and the current url."""
nargs = 0
hide = True
class TabOpen(Command):
"""Open a page in a new tab.
arg: The URL to open.
"""
nargs = 1
split_args = False
class TabOpenCur(Command):
"""Fill the statusbar with :tabopen and the current url."""
nargs = 0
hide = True
class TabClose(Command):
"""Close the current tab, or tab [count]."""
nargs = 0
count = True
class TabNext(Command):
"""Switch to the next tab, or skip [count] tabs."""
nargs = 0
count = True
class TabPrev(Command):
"""Switch to the previous tab, or skip [count] tabs."""
nargs = 0
count = True
class Quit(Command):
"""Quit qutebrowser."""
name = ['quit', 'q']
nargs = 0
class Reload(Command):
"""Reload the current page, or the page in tab [count]."""
nargs = 0
count = True
class Stop(Command):
"""Stop loading the current page, or the page in tab [count]."""
nargs = 0
count = True
class Back(Command):
"""Go back one/[count] page(s) in the history."""
nargs = 0
count = True
class Forward(Command):
"""Go forward one/[count] page(s) in the history."""
nargs = 0
count = True
class Print(Command):
"""Print the current page, or the page in tab [count]."""
nargs = 0
count = True
class Scroll(Command):
"""Scroll in x/y direction by a number of pixels.
arg 1: delta x
arg 2: delta y
count: multiplicator
"""
nargs = 2
count = True
hide = True
class ScrollPage(Command):
"""Scroll N pages up/down.
arg 1: pages in x-direction
arg 2: pages in y-direction
count: multiplicator
"""
nargs = 2
count = True
hide = True
name = 'scroll_page'
class Undo(Command):
"""Undo closing a tab."""
nargs = 0
class ScrollPercX(Command):
"""Scroll N percent horizontally.
optional arg: How many percent to scroll.
count: How many percent to scroll.
"""
nargs = '?'
count = True
hide = True
name = 'scroll_perc_x'
class ScrollPercY(Command):
"""Scroll N percent vertically.
optional arg: How many percent to scroll.
count: How many percent to scroll.
"""
nargs = '?'
count = True
hide = True
name = 'scroll_perc_y'
class PyEval(Command):
"""Evaluate python code.
arg: The python code to evaluate.
"""
nargs = 1
split_args = False
class NextSearch(Command):
"""Jump to the next or [count]th next search term."""
nargs = 0
hide = True
count = True
class Yank(Command):
"""Yank the URL of the current tab to the clipboard.
optional arg: If 'sel', yank to the primary selection.
"""
nargs = '?'
class YankTitle(Command):
"""Yank the title of the current tab to the clipboard.
optional arg: If 'sel', yank to the primary selection.
"""
nargs = '?'
class Paste(Command):
"""Open an URL from the clipboard.
optional arg: If 'sel', paste from the primary selection.
"""
nargs = '?'
class TabPaste(Command):
"""Open an URL from the clipboard in a new tab.
optional arg: If 'sel', paste from the primary selection.
"""
nargs = '?'
class ZoomIn(Command):
"""Zoom in in the current tab."""
nargs = 0
count = True
class ZoomOut(Command):
"""Zoom out in the current tab."""
nargs = 0
count = True
class Crash(Command):
"""Simply raise an exception for debugging."""
nargs = 0
hide = True
class Version(Command):
"""Show version information."""
nargs = 0
class SetTrace(Command):
"""Set pdb trace."""
nargs = 0
hide = True

View File

@ -19,20 +19,15 @@
import logging
from PyQt5.QtCore import pyqtSignal, QObject
from qutebrowser.commands.exceptions import ArgumentCountError
class Command(QObject):
class Command:
"""Base skeleton for a command.
See the module documentation for qutebrowser.commands.commands for details.
Signals:
signal: Emitted when the command was executed.
arg: A tuple (command, [args])
Attributes:
FIXME ...
"""
@ -40,13 +35,14 @@ class Command(QObject):
# we should probably have some kind of typing / argument casting for args
# this might be combined with help texts or so as well
def __init__(self, name, split_args, hide, nargs, count, handler):
def __init__(self, name, split_args, hide, nargs, count, desc, handler):
super().__init__()
self.name = name
self.split_args = split_args
self.hide = hide
self.nargs = nargs
self.count = count
self.desc = desc
self.handler = handler
def check(self, args):
@ -61,12 +57,11 @@ class Command(QObject):
ArgumentCountError if the argument count is wrong.
"""
if ((isinstance(self.nargs, int) and len(args) != self.nargs) or
(self.nargs == '?' and len(args) > 1) or
(self.nargs == '+' and len(args) < 1)):
# for nargs == '*', anything is okay
raise ArgumentCountError("{} args expected, but got {}".format(
self.nargs, len(args)))
if self.nargs[0] <= len(args) <= self.nargs[1]:
pass
else:
raise ArgumentCountError("{}-{} args expected, but got {}".format(
self.nargs[0], self.nargs[1], len(args)))
def run(self, args=None, count=None):
"""Run the command.
@ -75,18 +70,15 @@ class Command(QObject):
args: Arguments to the command.
count: Command repetition count.
Emit:
The command's signal.
"""
dbgout = ["command called:", self.mainname]
dbgout = ["command called:", self.name]
if args:
dbgout += args
if count is not None:
dbgout.append("(count={})".format(count))
logging.debug(' '.join(dbgout))
argv = [self.mainname]
if args is not None:
argv += args
self.signal.emit((count, argv))
if count is not None and self.count:
return self.handler(*args, count=count)
else:
return self.handler(*args)

View File

@ -23,8 +23,8 @@ import inspect
from PyQt5.QtCore import pyqtSlot, pyqtSignal, QObject
from PyQt5.QtWebKitWidgets import QWebPage
import qutebrowser.commands.commands
import qutebrowser.config.config as config
from qutebrowser.commands.template import Command
from qutebrowser.commands.exceptions import (ArgumentCountError,
NoSuchCommandError)
@ -32,54 +32,81 @@ from qutebrowser.commands.exceptions import (ArgumentCountError,
cmd_dict = {}
def register_cmd(func, name=None, split_args=True, hide=False):
"""Decorator to register a new command handler."""
global cmd_dict
names = []
names += newnames
if name is None:
name = func.__name__.lower()
if isinstance(name, str):
mainname = name
names.append(name)
else:
mainname = name[0]
names += name
count, nargs = _get_nargs_count(func)
cmd = Command(mainname, split_args, hide, nargs, count, handler=func)
for name in names:
cmd_dict[name] = cmd
return func
class register:
"""Decorator to register a new command handler.
def _get_nargs_count(self, func):
"""Get the number of command-arguments and count-support for a function.
Args:
func: The function to get the argcount for.
Return:
A (count, (minargs, maxargs)) tuple, with maxargs=None if there are
infinite args. count is True if the function supports count, else
False.
Mapping from old nargs format to (minargs, maxargs):
? (0, 1)
N (N, N)
+ (1, None)
* (0, None)
This could also be a function, but as a class (with a "wrong" name) it's
much cleaner to implement.
"""
# We could use inspect.signature maybe, but that's python >= 3.3 only.
spec = inspect.getfullargspec(func)
count = 'count' in spec.args
# we assume count always has a default (and it should!)
minargs = len(spec.args) - len(spec.defaults)
if spec.varargs is not None:
maxargs = None
else:
maxargs = len(spec.args) - int(count) # -1 if count is defined
return (count, (minargs, maxargs))
def __init__(self, name=None, nargs=None, split_args=True, hide=False):
self.name = name
self.split_args = split_args
self.hide = hide
self.nargs = nargs
def __call__(self, func):
global cmd_dict
names = []
name = func.__name__.lower() if self.name is None else self.name
if isinstance(name, str):
mainname = name
names.append(name)
else:
mainname = name[0]
names += name
count, nargs = self._get_nargs_count(func)
desc = func.__doc__.splitlines()[0].strip().rstrip('.')
cmd = Command(mainname, self.split_args, self.hide, nargs, count, desc,
handler=func)
for name in names:
cmd_dict[name] = cmd
return func
def _get_nargs_count(self, func):
"""Get the number of command-arguments and count-support for a func.
Args:
func: The function to get the argcount for.
Return:
A (count, (minargs, maxargs)) tuple, with maxargs=None if there are
infinite args. count is True if the function supports count, else
False.
Mapping from old nargs format to (minargs, maxargs):
? (0, 1)
N (N, N)
+ (1, None)
* (0, None)
"""
# We could use inspect.signature maybe, but that's python >= 3.3 only.
spec = inspect.getfullargspec(func)
count = 'count' in spec.args
# we assume count always has a default (and it should!)
if self.nargs is not None:
# If nargs is overriden, use that.
if isinstance(self.nargs, int):
# Single int
minargs, maxargs = self.nargs, self.nargs
else:
# Tuple (min, max)
minargs, maxargs = self.nargs
else:
defaultcount = (len(spec.defaults) if spec.defaults is not None
else 0)
argcount = len(spec.args)
if 'self' in spec.args:
argcount -= 1
minargs = argcount - defaultcount
if spec.varargs is not None:
maxargs = None
else:
maxargs = len(spec.args) - int(count) # -1 if count is defined
return (count, (minargs, maxargs))
class SearchParser(QObject):
@ -147,6 +174,7 @@ class SearchParser(QObject):
"""
self._search(text, rev=True)
@register(hide=True)
def nextsearch(self, count=1):
"""Continue the search to the ([count]th) next term.

View File

@ -35,8 +35,7 @@ class CommandCompletionModel(CompletionModel):
cmdlist = []
for obj in set(cmd_dict.values()):
if not obj.hide:
doc = obj.__doc__.splitlines()[0].strip().rstrip('.')
cmdlist.append([obj.mainname, doc])
cmdlist.append([obj.name, obj.desc])
data = OrderedDict()
data['Commands'] = sorted(cmdlist)
self.init_data(data)

View File

@ -22,6 +22,8 @@ import os.path
from PyQt5.QtCore import pyqtRemoveInputHook
#import qutebrowser.commands.utils as cmdutils
try:
# pylint: disable=import-error
from ipdb import set_trace as pdb_set_trace
@ -30,10 +32,10 @@ except ImportError:
import qutebrowser
# FIXME we can';t do this because of circular imports
#@cmdutils.register(name='settrace', hide=True)
def set_trace():
"""
Set a tracepoint in the Python debugger that works with Qt.
"""Set a tracepoint in the Python debugger that works with Qt.
Based on http://stackoverflow.com/a/1745965/2085149

View File

@ -36,6 +36,7 @@ from PyQt5.QtWebKitWidgets import QWebView, QWebPage
import qutebrowser.utils.url as urlutils
import qutebrowser.config.config as config
import qutebrowser.commands.utils as cmdutils
from qutebrowser.widgets.tabbar import TabWidget
from qutebrowser.network.networkmanager import NetworkManager
from qutebrowser.utils.signals import SignalCache, dbg_signal
@ -246,6 +247,7 @@ class TabbedBrowser(TabWidget):
tab.shutdown(callback=functools.partial(self._cb_tab_shutdown,
tab))
@cmdutils.register()
def tabclose(self, count=None):
"""Close the current/[count]th tab.
@ -275,6 +277,7 @@ class TabbedBrowser(TabWidget):
elif last_close == 'blank':
tab.openurl('about:blank')
@cmdutils.register(split_args=False)
def tabopen(self, url):
"""Open a new tab with a given url.
@ -308,6 +311,7 @@ class TabbedBrowser(TabWidget):
tab.open_tab.connect(self.tabopen)
tab.openurl(url)
@cmdutils.register(hide=True)
def tabopencur(self):
"""Set the statusbar to :tabopen and the current URL.
@ -318,6 +322,7 @@ class TabbedBrowser(TabWidget):
url = urlutils.urlstring(self.currentWidget().url())
self.set_cmd_text.emit(':tabopen ' + url)
@cmdutils.register(hide=True)
def opencur(self):
"""Set the statusbar to :open and the current URL.
@ -328,8 +333,9 @@ class TabbedBrowser(TabWidget):
url = urlutils.urlstring(self.currentWidget().url())
self.set_cmd_text.emit(':open ' + url)
@cmdutils.register(name='undo')
def undo_close(self):
"""Undo closing a tab.
"""Switch to the previous tab, or skip [count] tabs.
Command handler for :undo.
@ -337,6 +343,7 @@ class TabbedBrowser(TabWidget):
if self._url_stack:
self.tabopen(self._url_stack.pop())
@cmdutils.register(name='tabprev')
def switch_prev(self, count=1):
"""Switch to the ([count]th) previous tab.
@ -353,8 +360,9 @@ class TabbedBrowser(TabWidget):
# FIXME
pass
@cmdutils.register('tabnext')
def switch_next(self, count=1):
"""Switch to the ([count]th) next tab.
"""Switch to the next tab, or skip [count] tabs.
Command handler for :tabnext.
@ -369,6 +377,7 @@ class TabbedBrowser(TabWidget):
# FIXME
pass
@cmdutils.register()
def paste(self, sel=False):
"""Open a page from the clipboard.
@ -385,6 +394,7 @@ class TabbedBrowser(TabWidget):
logging.debug("Clipboard contained: '{}'".format(url))
self.openurl(url)
@cmdutils.register()
def tabpaste(self, sel=False):
"""Open a page from the clipboard in a new tab.
@ -479,6 +489,7 @@ class CurCommandDispatcher(QObject):
return
frame.setScrollBarValue(orientation, int(m * perc / 100))
@cmdutils.register(name='open', split_args=False)
def openurl(self, url, count=None):
"""Open an url in the current/[count]th tab.
@ -501,6 +512,7 @@ class CurCommandDispatcher(QObject):
else:
tab.openurl(url)
@cmdutils.register(name='reload')
def reloadpage(self, count=None):
"""Reload the current/[count]th tab.
@ -514,6 +526,7 @@ class CurCommandDispatcher(QObject):
if tab is not None:
tab.reload()
@cmdutils.register()
def stop(self, count=None):
"""Stop loading in the current/[count]th tab.
@ -527,6 +540,7 @@ class CurCommandDispatcher(QObject):
if tab is not None:
tab.stop()
@cmdutils.register(name='print')
def printpage(self, count=None):
"""Print the current/[count]th tab.
@ -543,6 +557,7 @@ class CurCommandDispatcher(QObject):
preview.paintRequested.connect(tab.print)
preview.exec_()
@cmdutils.register()
def back(self, count=1):
"""Go back in the history of the current tab.
@ -556,6 +571,7 @@ class CurCommandDispatcher(QObject):
for i in range(count): # pylint: disable=unused-variable
self.tabs.currentWidget().back()
@cmdutils.register()
def forward(self, count=1):
"""Go forward in the history of the current tab.
@ -580,6 +596,7 @@ class CurCommandDispatcher(QObject):
"""
self.tabs.currentWidget().findText(text, flags)
@cmdutils.register(hide=True)
def scroll(self, dx, dy, count=1):
"""Scroll the current tab by count * dx/dy.
@ -595,8 +612,9 @@ class CurCommandDispatcher(QObject):
dy = int(count) * float(dy)
self.tabs.currentWidget().page_.mainFrame().scroll(dx, dy)
@cmdutils.register(name='scroll_perc_x', hide=True)
def scroll_percent_x(self, perc=None, count=None):
"""Scroll the current tab to a specific percent of the page.
"""Scroll the current tab to a specific percent of the page (horiz).
Command handler for :scroll_perc_x.
@ -607,8 +625,9 @@ class CurCommandDispatcher(QObject):
"""
self._scroll_percent(perc, count, Qt.Horizontal)
@cmdutils.register(name='scroll_perc_y', hide=True)
def scroll_percent_y(self, perc=None, count=None):
"""Scroll the current tab to a specific percent of the page.
"""Scroll the current tab to a specific percent of the page (vert).
Command handler for :scroll_perc_y
@ -619,6 +638,7 @@ class CurCommandDispatcher(QObject):
"""
self._scroll_percent(perc, count, Qt.Vertical)
@cmdutils.register(hide=True)
def scroll_page(self, mx, my, count=1):
"""Scroll the frame page-wise.
@ -634,6 +654,7 @@ class CurCommandDispatcher(QObject):
page.mainFrame().scroll(int(count) * float(mx) * size.width(),
int(count) * float(my) * size.height())
@cmdutils.register()
def yank(self, sel=False):
"""Yank the current url to the clipboard or primary selection.
@ -653,6 +674,7 @@ class CurCommandDispatcher(QObject):
self.temp_message.emit('URL yanked to {}'.format(
'primary selection' if sel else 'clipboard'))
@cmdutils.register(name='yanktitle')
def yank_title(self, sel=False):
"""Yank the current title to the clipboard or primary selection.
@ -672,6 +694,7 @@ class CurCommandDispatcher(QObject):
self.temp_message.emit('Title yanked to {}'.format(
'primary selection' if sel else 'clipboard'))
@cmdutils.register(name='zoomin')
def zoom_in(self, count=1):
"""Zoom in in the current tab.
@ -682,6 +705,7 @@ class CurCommandDispatcher(QObject):
tab = self.tabs.currentWidget()
tab.zoom(count)
@cmdutils.register(name='zoomout')
def zoom_out(self, count=1):
"""Zoom out in the current tab.