From 265d8b758030f51d7a45cdaf78d4af425a47604c Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Mon, 24 Feb 2014 19:11:43 +0100 Subject: [PATCH] Avoid circular dependencies --- qutebrowser/app.py | 5 +- qutebrowser/commands/exceptions.py | 2 +- qutebrowser/commands/keys.py | 4 +- qutebrowser/commands/parsers.py | 228 +++++++++++++++++++++++++++++ qutebrowser/commands/utils.py | 4 +- qutebrowser/config/templates.py | 7 +- 6 files changed, 240 insertions(+), 10 deletions(-) create mode 100644 qutebrowser/commands/parsers.py diff --git a/qutebrowser/app.py b/qutebrowser/app.py index 1cd3a5973..2a5a0bed8 100644 --- a/qutebrowser/app.py +++ b/qutebrowser/app.py @@ -52,6 +52,7 @@ import qutebrowser.network.qutescheme as qutescheme from qutebrowser.widgets.mainwindow import MainWindow from qutebrowser.widgets.crash import CrashDialog from qutebrowser.commands.keys import KeyParser +from qutebrowser.commands.parsers import CommandParser, SearchParser from qutebrowser.utils.appdirs import AppDirs from qutebrowser.utils.misc import dotted_getattr from qutebrowser.utils.debug import set_trace # noqa pylint: disable=unused-import @@ -100,8 +101,8 @@ class QuteBrowser(QApplication): confdir = self._args.confdir config.init(confdir) - self.commandparser = cmdutils.CommandParser() - self.searchparser = cmdutils.SearchParser() + self.commandparser = CommandParser() + self.searchparser = SearchParser() self.keyparser = KeyParser(self) self._init_cmds() self.mainwindow = MainWindow() diff --git a/qutebrowser/commands/exceptions.py b/qutebrowser/commands/exceptions.py index 83251f457..1f4bb0ad7 100644 --- a/qutebrowser/commands/exceptions.py +++ b/qutebrowser/commands/exceptions.py @@ -15,7 +15,7 @@ # You should have received a copy of the GNU General Public License # along with qutebrowser. If not, see . -"""Exception classes for commands.utils and commands.command. +"""Exception classes for commands modules. Defined here to avoid circular dependency hell. diff --git a/qutebrowser/commands/keys.py b/qutebrowser/commands/keys.py index f7915747a..eb4f541d9 100644 --- a/qutebrowser/commands/keys.py +++ b/qutebrowser/commands/keys.py @@ -23,8 +23,8 @@ import logging from PyQt5.QtCore import pyqtSignal, Qt, QObject from PyQt5.QtGui import QKeySequence -from qutebrowser.commands.utils import (CommandParser, ArgumentCountError, - NoSuchCommandError) +from qutebrowser.commands.parsers import (CommandParser, ArgumentCountError, + NoSuchCommandError) # Possible chars for starting a commandline input STARTCHARS = ":/?" diff --git a/qutebrowser/commands/parsers.py b/qutebrowser/commands/parsers.py new file mode 100644 index 000000000..ba5dc043e --- /dev/null +++ b/qutebrowser/commands/parsers.py @@ -0,0 +1,228 @@ +# Copyright 2014 Florian Bruhin (The Compiler) +# +# This file is part of qutebrowser. +# +# qutebrowser is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# qutebrowser is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with qutebrowser. If not, see . + +"""Module containing commandline parsers ( SearchParser and CommandParser).""" + +import shlex + +from PyQt5.QtCore import pyqtSlot, pyqtSignal, QObject + +import qutebrowser.config.config as config +import qutebrowser.commands.utils as cmdutils +from qutebrowser.commands.exceptions import (ArgumentCountError, + NoSuchCommandError) + +class SearchParser(QObject): + + """Parse qutebrowser searches. + + Attributes: + _text: The text from the last search. + _flags: The flags from the last search. + + Signals: + do_search: Emitted when a search should be started. + arg 1: Search string. + arg 2: Flags to use. + + """ + + do_search = pyqtSignal(str, 'QWebPage::FindFlags') + + def __init__(self, parent=None): + self._text = None + self._flags = 0 + super().__init__(parent) + + def _search(self, text, rev=False): + """Search for a text on the current page. + + Args: + text: The text to search for. + rev: Search direction, True if reverse, else False. + + Emit: + do_search: If a search should be started. + + """ + if self._text is not None and self._text != text: + self.do_search.emit('', 0) + self._text = text + self._flags = 0 + if config.config.getboolean('general', 'ignorecase', fallback=True): + self._flags |= QWebPage.FindCaseSensitively + if config.config.getboolean('general', 'wrapsearch', fallback=True): + self._flags |= QWebPage.FindWrapsAroundDocument + if rev: + self._flags |= QWebPage.FindBackward + self.do_search.emit(self._text, self._flags) + + @pyqtSlot(str) + def search(self, text): + """Search for a text on a website. + + Args: + text: The text to search for. + + """ + self._search(text) + + @pyqtSlot(str) + def search_rev(self, text): + """Search for a text on a website in reverse direction. + + Args: + text: The text to search for. + + """ + self._search(text, rev=True) + + def nextsearch(self, count=1): + """Continue the search to the ([count]th) next term. + + Args: + count: How many elements to ignore. + + Emit: + do_search: If a search should be started. + + """ + if self._text is not None: + for i in range(count): # pylint: disable=unused-variable + self.do_search.emit(self._text, self._flags) + + +class CommandParser(QObject): + + """Parse qutebrowser commandline commands. + + Attributes: + _cmd: The command which was parsed. + _args: The arguments which were parsed. + + Signals: + error: Emitted if there was an error. + arg: The error message. + + """ + + error = pyqtSignal(str) + + def __init__(self, parent=None): + super().__init__(parent) + self._cmd = None + self._args = [] + + def _parse(self, text, aliases=True): + """Split the commandline text into command and arguments. + + Args: + text: Text to parse. + aliases: Whether to handle aliases. + + Raise: + NoSuchCommandError if a command wasn't found. + + """ + parts = text.strip().split(maxsplit=1) + if not parts: + raise NoSuchCommandError("No command given") + cmdstr = parts[0] + if aliases: + try: + alias = config.config.get('aliases', cmdstr) + except config.NoOptionError: + pass + else: + return self._parse(alias, aliases=False) + try: + cmd = cmdutils.cmd_dict[cmdstr] + except KeyError: + raise NoSuchCommandError("Command {} not found.".format(cmdstr)) + + if len(parts) == 1: + args = [] + elif cmd.split_args: + args = shlex.split(parts[1]) + else: + args = [parts[1]] + self._cmd = cmd + self._args = args + + def _check(self): + """Check if the argument count for the command is correct.""" + self._cmd.check(self._args) + + def _run(self, count=None): + """Run a command with an optional count. + + Args: + count: Count to pass to the command. + + """ + if count is not None: + self._cmd.run(self._args, count=count) + else: + self._cmd.run(self._args) + + @pyqtSlot(str, int, bool) + def run(self, text, count=None, ignore_exc=True): + """Parse a command from a line of text. + + If ignore_exc is True, ignore exceptions and return True/False. + + Args: + text: The text to parse. + count: The count to pass to the command. + ignore_exc: Ignore exceptions and return False instead. + + Raise: + NoSuchCommandError: if a command wasn't found. + ArgumentCountError: if a command was called with the wrong count of + arguments. + + Return: + True if command was called (handler returnstatus is ignored!). + False if command wasn't called (there was an ignored exception). + + Emit: + error: If there was an error parsing a command. + + """ + if ';;' in text: + retvals = [] + for sub in text.split(';;'): + retvals.append(self.run(sub, count, ignore_exc)) + return all(retvals) + try: + self._parse(text) + self._check() + except ArgumentCountError: + if ignore_exc: + self.error.emit("{}: invalid argument count".format( + self._cmd.mainname)) + return False + else: + raise + except NoSuchCommandError as e: + if ignore_exc: + self.error.emit("{}: no such command".format(e)) + return False + else: + raise + self._run(count=count) + return True diff --git a/qutebrowser/commands/utils.py b/qutebrowser/commands/utils.py index 7cfbaaa0e..5ca99f55e 100644 --- a/qutebrowser/commands/utils.py +++ b/qutebrowser/commands/utils.py @@ -15,12 +15,10 @@ # You should have received a copy of the GNU General Public License # along with qutebrowser. If not, see . -"""Contains various command utils, and the CommandParser.""" +"""Contains various command utils and a global command dict.""" -import shlex import inspect -from PyQt5.QtCore import pyqtSlot, pyqtSignal, QObject from PyQt5.QtWebKitWidgets import QWebPage import qutebrowser.config.config as config diff --git a/qutebrowser/config/templates.py b/qutebrowser/config/templates.py index 4141d0f27..8d31f4e8b 100644 --- a/qutebrowser/config/templates.py +++ b/qutebrowser/config/templates.py @@ -135,10 +135,13 @@ class CommandSettingValue(SettingValue): values = cmdutils.cmd_dict.values() def validate(self, value): - cp = cmdutils.CommandParser() + # We need to import this here to avoid circular dependencies + from qutebrowser.commands.parsers import (CommandParser, + NoSuchCommandError) + cp = CommandParser() try: cp.parse(value) - except cmdutils.NoSuchCommandError: + except NoSuchCommandError: return False else: return True