diff --git a/README.asciidoc b/README.asciidoc index eb6547581..879463ed4 100644 --- a/README.asciidoc +++ b/README.asciidoc @@ -69,7 +69,7 @@ Requirements The following software and libraries are required to run qutebrowser: -* http://www.python.org/[Python] 3.3 or newer (3.4 recommended) +* http://www.python.org/[Python] 3.4 * http://qt-project.org/[Qt] 5.2 or newer (5.3.1 recommended) * QtWebKit * http://www.riverbankcomputing.com/software/pyqt/intro[PyQt] 5.2 or newer diff --git a/pkg/PKGBUILD.qutebrowser-git b/pkg/PKGBUILD.qutebrowser-git index 9c06be0eb..18f62593e 100644 --- a/pkg/PKGBUILD.qutebrowser-git +++ b/pkg/PKGBUILD.qutebrowser-git @@ -8,7 +8,7 @@ pkgdesc="A keyboard-driven, vim-like browser based on PyQt5 and QtWebKit.", arch=(any) url="http://www.qutebrowser.org/" license=('GPL') -depends=('python>=3.3' 'python-setuptools' 'python-pyqt5>=5.2' 'qt5-base>=5.2' +depends=('python>=3.4' 'python-setuptools' 'python-pyqt5>=5.2' 'qt5-base>=5.2' 'qt5-webkit>=5.2' 'libxkbcommon-x11' 'python-rfc6266') makedepends=('python' 'python-setuptools') optdepends=('python-colorlog: colored logging output') diff --git a/qutebrowser/browser/hints.py b/qutebrowser/browser/hints.py index 1fc93b9a1..707c29006 100644 --- a/qutebrowser/browser/hints.py +++ b/qutebrowser/browser/hints.py @@ -39,8 +39,8 @@ from qutebrowser.utils.qt import qt_ensure_valid ElemTuple = namedtuple('ElemTuple', 'elem, label') -Target = enum('normal', 'tab', 'tab_bg', 'yank', 'yank_primary', 'cmd', - 'cmd_tab', 'cmd_tab_bg', 'rapid', 'download') +Target = enum('Target', 'normal', 'tab', 'tab_bg', 'yank', 'yank_primary', + 'cmd', 'cmd_tab', 'cmd_tab_bg', 'rapid', 'download') class HintContext: @@ -280,7 +280,7 @@ class HintManager(QObject): target = Target.tab_bg else: target = self._context.target - self.set_open_target.emit(Target[target]) + self.set_open_target.emit(target.name) # FIXME Instead of clicking the center, we could have nicer heuristics. # e.g. parse (-webkit-)border-radius correctly and click text fields at # the bottom right, and everything else on the top left or so. diff --git a/qutebrowser/config/websettings.py b/qutebrowser/config/websettings.py index 58cbb1e5b..14a6c3087 100644 --- a/qutebrowser/config/websettings.py +++ b/qutebrowser/config/websettings.py @@ -34,7 +34,7 @@ import qutebrowser.config.config as config from qutebrowser.utils.usertypes import enum from qutebrowser.utils.misc import get_standard_dir -MapType = enum('attribute', 'setter', 'static_setter') +MapType = enum('MapType', 'attribute', 'setter', 'static_setter') MAPPINGS = { diff --git a/qutebrowser/keyinput/basekeyparser.py b/qutebrowser/keyinput/basekeyparser.py index c0d91b08f..4f5046c3d 100644 --- a/qutebrowser/keyinput/basekeyparser.py +++ b/qutebrowser/keyinput/basekeyparser.py @@ -71,8 +71,8 @@ class BaseKeyParser(QObject): keystring_updated = pyqtSignal(str) do_log = True - Match = enum('partial', 'definitive', 'ambiguous', 'none') - Type = enum('chain', 'special') + Match = enum('Match', 'partial', 'definitive', 'ambiguous', 'none') + Type = enum('Type', 'chain', 'special') def __init__(self, parent=None, supports_count=None, supports_chains=False): diff --git a/qutebrowser/keyinput/modeparsers.py b/qutebrowser/keyinput/modeparsers.py index 8a0cadaac..bf250aff6 100644 --- a/qutebrowser/keyinput/modeparsers.py +++ b/qutebrowser/keyinput/modeparsers.py @@ -33,7 +33,7 @@ from qutebrowser.utils.log import keyboard as logger STARTCHARS = ":/?" -LastPress = enum('none', 'filtertext', 'keystring') +LastPress = enum('LastPress', 'none', 'filtertext', 'keystring') class NormalKeyParser(CommandKeyParser): @@ -120,8 +120,8 @@ class HintKeyParser(CommandKeyParser): e.key(), e.text())) if e.key() == Qt.Key_Backspace: logger.debug("Got backspace, mode {}, filtertext '{}', keystring " - "'{}'".format(LastPress[self._last_press], - self._filtertext, self._keystring)) + "'{}'".format(self._last_press, self._filtertext, + self._keystring)) if self._last_press == LastPress.filtertext and self._filtertext: self._filtertext = self._filtertext[:-1] self.filter_hints.emit(self._filtertext) diff --git a/qutebrowser/models/basecompletion.py b/qutebrowser/models/basecompletion.py index 6d0453f03..c72f558bc 100644 --- a/qutebrowser/models/basecompletion.py +++ b/qutebrowser/models/basecompletion.py @@ -30,7 +30,7 @@ from qutebrowser.utils.usertypes import enum from qutebrowser.utils.qt import qt_ensure_valid -Role = enum('marks', 'sort', start=Qt.UserRole) +Role = enum('Role', 'marks', 'sort', start=Qt.UserRole, is_int=True) class BaseCompletionModel(QStandardItemModel): diff --git a/qutebrowser/models/downloadmodel.py b/qutebrowser/models/downloadmodel.py index 2902636ea..6cd5dc5c3 100644 --- a/qutebrowser/models/downloadmodel.py +++ b/qutebrowser/models/downloadmodel.py @@ -28,7 +28,7 @@ from qutebrowser.utils.usertypes import enum from qutebrowser.utils.qt import qt_ensure_valid -Role = enum('item', start=Qt.UserRole) +Role = enum('Role', 'item', start=Qt.UserRole, is_int=True) class DownloadModel(QAbstractListModel): diff --git a/qutebrowser/test/utils/usertypes/test_enum.py b/qutebrowser/test/utils/usertypes/test_enum.py index 707b967ee..1ce3a9258 100644 --- a/qutebrowser/test/utils/usertypes/test_enum.py +++ b/qutebrowser/test/utils/usertypes/test_enum.py @@ -23,6 +23,8 @@ import unittest from qutebrowser.utils.usertypes import enum +# FIXME: Add some more tests, e.g. for is_int + class EnumTests(unittest.TestCase): @@ -33,33 +35,28 @@ class EnumTests(unittest.TestCase): """ def setUp(self): - self.enum = enum('zero', 'one') + self.enum = enum('Enum', 'one', 'two') def test_values(self): """Test if enum members resolve to the right values.""" - self.assertEqual(self.enum.zero, 0) - self.assertEqual(self.enum.one, 1) + self.assertEqual(self.enum.one.value, 1) + self.assertEqual(self.enum.two.value, 2) - def test_reverse(self): - """Test reverse mapping.""" - self.assertEqual(self.enum[0], 'zero') - self.assertEqual(self.enum[1], 'one') + def test_name(self): + """Test .name mapping.""" + self.assertEqual(self.enum.one.name, 'one') + self.assertEqual(self.enum.two.name, 'two') def test_unknown(self): """Test invalid values which should raise an AttributeError.""" with self.assertRaises(AttributeError): - _ = self.enum.two - - def test_unknown_reverse(self): - """Test reverse mapping with invalid value .""" - with self.assertRaises(KeyError): - _ = self.enum['two'] + _ = self.enum.three def test_start(self): """Test the start= argument.""" - e = enum('three', 'four', start=3) - self.assertEqual(e.three, 3) - self.assertEqual(e.four, 4) + e = enum('Enum', 'three', 'four', start=3) + self.assertEqual(e.three.value, 3) + self.assertEqual(e.four.value, 4) if __name__ == '__main__': diff --git a/qutebrowser/utils/checkpyver.py b/qutebrowser/utils/checkpyver.py index fcc889bf2..ae4622c06 100644 --- a/qutebrowser/utils/checkpyver.py +++ b/qutebrowser/utils/checkpyver.py @@ -40,11 +40,11 @@ except ImportError: # to stderr. def check_python_version(): """Check if correct python version is run.""" - if sys.hexversion < 0x03030000: + if sys.hexversion < 0x03040000: # We don't use .format() and print_function here just in case someone # still has < 2.6 installed. version_str = '.'.join(map(str, sys.version_info[:3])) - text = ("At least Python 3.3 is required to run qutebrowser, but " + + text = ("At least Python 3.4 is required to run qutebrowser, but " + version_str + " is installed!\n") if Tk: root = Tk() diff --git a/qutebrowser/utils/earlyinit.py b/qutebrowser/utils/earlyinit.py index 3a4605852..85d1ecc0a 100644 --- a/qutebrowser/utils/earlyinit.py +++ b/qutebrowser/utils/earlyinit.py @@ -17,7 +17,7 @@ """Things which need to be done really early (e.g. before importing Qt). -At this point we can be sure we have all python 3.3 features available. +At this point we can be sure we have all python 3.4 features available. """ import os diff --git a/qutebrowser/utils/usertypes.py b/qutebrowser/utils/usertypes.py index 2cf5a75d5..5d17347d5 100644 --- a/qutebrowser/utils/usertypes.py +++ b/qutebrowser/utils/usertypes.py @@ -25,6 +25,7 @@ Module attributes: import operator import collections.abc +import enum as pyenum from PyQt5.QtCore import pyqtSignal, QObject, QTimer @@ -34,21 +35,20 @@ from qutebrowser.utils.log import misc as logger _UNSET = object() -def enum(*items, start=0): +def enum(name, *items, start=1, is_int=False): """Factory for simple enumerations. - We really don't need more complex things here, so we don't use python3.4's - enum, because we'd have to backport things to 3.3. - - Based on: http://stackoverflow.com/a/1695250/2085149 - Args: + name: Name of the enum *items: Items to be sequentally enumerated. start: The number to use for the first value. + We use 1 as default so enum members are always True. + is_init: True if the enum should be a Python IntEnum """ - numbers = range(start, len(items) + start) - enums = dict(zip(items, numbers)) - return EnumBase('Enum', (), enums) + enums = [(v, i) for (i, v) in enumerate(items, start)] + base = pyenum.IntEnum if is_int else pyenum.Enum + base = pyenum.unique(base) + return base(name, enums) class EnumBase(type): @@ -77,7 +77,7 @@ class NeighborList(collections.abc.Sequence): _mode: The current mode. """ - Modes = enum('block', 'wrap', 'exception') + Modes = enum('Modes', 'block', 'wrap', 'exception') def __init__(self, items=None, default=_UNSET, mode=Modes.exception): """Constructor. @@ -233,10 +233,10 @@ class NeighborList(collections.abc.Sequence): # The mode of a Question. -PromptMode = enum('yesno', 'text', 'user_pwd', 'alert') +PromptMode = enum('PromptMode', 'yesno', 'text', 'user_pwd', 'alert') # Where to open a clicked link. -ClickTarget = enum('normal', 'tab', 'tab_bg') +ClickTarget = enum('ClickTarget', 'normal', 'tab', 'tab_bg') class Question(QObject): diff --git a/qutebrowser/utils/webelem.py b/qutebrowser/utils/webelem.py index 15163b7de..c100ff288 100644 --- a/qutebrowser/utils/webelem.py +++ b/qutebrowser/utils/webelem.py @@ -36,7 +36,8 @@ from qutebrowser.utils.usertypes import enum from qutebrowser.utils.misc import compact_text -Group = enum('all', 'links', 'images', 'editable', 'url', 'prevnext', 'focus') +Group = enum('Group', 'all', 'links', 'images', 'editable', 'url', 'prevnext', + 'focus') SELECTORS = { diff --git a/qutebrowser/widgets/statusbar/url.py b/qutebrowser/widgets/statusbar/url.py index f254d4fb2..aa4c4c592 100644 --- a/qutebrowser/widgets/statusbar/url.py +++ b/qutebrowser/widgets/statusbar/url.py @@ -24,6 +24,11 @@ from PyQt5.QtCore import pyqtSlot, pyqtProperty, Qt from qutebrowser.widgets.webview import LoadStatus from qutebrowser.widgets.statusbar.textbase import TextBase from qutebrowser.config.style import set_register_stylesheet, get_stylesheet +from qutebrowser.utils.usertypes import enum + + +# Note this has entries for success/error/warn from widgets.webview:LoadStatus +UrlType = enum('UrlType', 'success', 'error', 'warn', 'hover', 'normal') class UrlText(TextBase): @@ -34,8 +39,8 @@ class UrlText(TextBase): STYLESHEET: The stylesheet template. Attributes: - normal_url: The normal URL to be displayed. - normal_url_type: The type of the normal URL. + normal_url: The normal URL to be displayed as a UrlType instance. + normal_url_type: The type of the normal URL as a UrlType instance. hover_url: The URL we're currently hovering over. _ssl_errors: Whether SSL errors occured while loading. @@ -78,13 +83,20 @@ class UrlText(TextBase): set_register_stylesheet(self) self._hover_url = None self._normal_url = None - self._normal_url_type = 'normal' + self._normal_url_type = UrlType.normal @pyqtProperty(str) def urltype(self): - """Getter for self.urltype, so it can be used as Qt property.""" + """Getter for self.urltype, so it can be used as Qt property. + + Return: + The urltype as a string (!) + """ # pylint: disable=method-hidden - return self._urltype + if self._urltype is None: + return "" + else: + return self._urltype.name @urltype.setter def urltype(self, val): @@ -121,7 +133,11 @@ class UrlText(TextBase): @normal_url_type.setter def normal_url_type(self, val): - """Setter to update displayed URL when normal_url_type was set.""" + """Setter to update displayed URL when normal_url_type was set. + + Args: + val: The value as an UrlType instance. + """ self._normal_url_type = val self._update_url() @@ -129,25 +145,26 @@ class UrlText(TextBase): """Update the displayed URL if the url or the hover url changed.""" if self.hover_url is not None: self.setText(self.hover_url) - self.urltype = 'hover' + self.urltype = UrlType.hover elif self.normal_url is not None: self.setText(self.normal_url) self.urltype = self.normal_url_type else: self.setText('') - self.urltype = 'normal' + self.urltype = UrlType.normal @pyqtSlot(str) - def on_load_status_changed(self, status): + def on_load_status_changed(self, status_str): """Slot for load_status_changed. Sets URL color accordingly. Args: - status: The LoadStatus as string. + status_str: The LoadStatus as string. """ - if status in ('success', 'error', 'warn'): - self.normal_url_type = status + status = LoadStatus[status_str] + if status in (LoadStatus.success, LoadStatus.error, LoadStatus.warn): + self.normal_url_type = UrlType[status_str] else: - self.normal_url_type = 'normal' + self.normal_url_type = UrlType.normal @pyqtSlot(str) def set_url(self, s): @@ -157,7 +174,7 @@ class UrlText(TextBase): s: The URL to set as string. """ self.normal_url = s - self.normal_url_type = 'normal' + self.normal_url_type = UrlType.normal @pyqtSlot(str, str, str) def set_hover_url(self, link, _title, _text): @@ -181,8 +198,4 @@ class UrlText(TextBase): """Update URL if the tab changed.""" self.hover_url = None self.normal_url = tab.url_text - status = LoadStatus[tab.load_status] - if status in ('success', 'error', 'warn'): - self.normal_url_type = status - else: - self.normal_url_type = 'normal' + self.on_load_status_changed(tab.load_status.name) diff --git a/qutebrowser/widgets/webview.py b/qutebrowser/widgets/webview.py index 7c4d7027c..d238a6abe 100644 --- a/qutebrowser/widgets/webview.py +++ b/qutebrowser/widgets/webview.py @@ -39,7 +39,7 @@ from qutebrowser.utils.usertypes import NeighborList, ClickTarget, enum from qutebrowser.commands.exceptions import CommandError -LoadStatus = enum('none', 'success', 'error', 'warn', 'loading') +LoadStatus = enum('LoadStatus', 'none', 'success', 'error', 'warn', 'loading') class WebView(QWebView): @@ -138,10 +138,9 @@ class WebView(QWebView): Emit: load_status_changed """ - log.webview.debug("load status for {}: {}".format( - repr(self), LoadStatus[val])) + log.webview.debug("load status for {}: {}".format(repr(self), val)) self._load_status = val - self.load_status_changed.emit(LoadStatus[val]) + self.load_status_changed.emit(val.name) @property def url_text(self): @@ -262,7 +261,7 @@ class WebView(QWebView): self.open_target = self._force_open_target self._force_open_target = None log.mouse.debug("Setting force target: {}".format( - ClickTarget[self.open_target])) + self.open_target)) elif (e.button() == Qt.MidButton or e.modifiers() & Qt.ControlModifier): if config.get('general', 'background-tabs'): @@ -270,7 +269,7 @@ class WebView(QWebView): else: self.open_target = ClickTarget.tab log.mouse.debug("Middle click, setting target: {}".format( - ClickTarget[self.open_target])) + self.open_target)) else: self.open_target = ClickTarget.normal log.mouse.debug("Normal click, setting normal target") diff --git a/scripts/generate_doc.py b/scripts/generate_doc.py index 16f58fdc5..244c1f8e7 100644 --- a/scripts/generate_doc.py +++ b/scripts/generate_doc.py @@ -54,8 +54,8 @@ def _parse_docstring(func): # noqa A (short_desc, long_desc, arg_descs) tuple. """ # pylint: disable=too-many-branches - State = enum('short', 'desc', # pylint: disable=invalid-name - 'desc_hidden', 'arg_start', 'arg_inside', 'misc') + State = enum('State', 'short', 'desc', 'desc_hidden', 'arg_start', + 'arg_inside', 'misc') doc = inspect.getdoc(func) lines = doc.splitlines() diff --git a/scripts/setupcommon.py b/scripts/setupcommon.py index bb86c4d91..e15d5441b 100644 --- a/scripts/setupcommon.py +++ b/scripts/setupcommon.py @@ -111,7 +111,6 @@ setupdata = { 'Operating System :: Microsoft :: Windows :: Windows 7', 'Operating System :: POSIX :: Linux', 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.3', 'Programming Language :: Python :: 3.4', 'Topic :: Internet', 'Topic :: Internet :: WWW/HTTP',