From 6f1b8bd1d92c68cb6dfc51228499b4bb99ba6ec9 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sun, 17 Sep 2017 20:06:35 +0200 Subject: [PATCH 01/50] Make sure the config is available before the QApplication See #2589 --- qutebrowser/app.py | 8 ++-- qutebrowser/config/config.py | 44 +++++++++++++------- qutebrowser/config/configfiles.py | 18 ++++++-- qutebrowser/misc/savemanager.py | 11 +---- tests/unit/config/test_config.py | 69 ++++++++++++++++++++----------- 5 files changed, 95 insertions(+), 55 deletions(-) diff --git a/qutebrowser/app.py b/qutebrowser/app.py index 3dbb72dc8..3f24af721 100644 --- a/qutebrowser/app.py +++ b/qutebrowser/app.py @@ -73,6 +73,9 @@ def run(args): log.init.debug("Initializing directories...") standarddir.init(args) + log.init.debug("Initializing config...") + config.early_init() + global qApp qApp = Application(args) qApp.setOrganizationName("qutebrowser") @@ -408,6 +411,7 @@ def _init_modules(args, crash_handler): save_manager = savemanager.SaveManager(qApp) objreg.register('save-manager', save_manager) save_manager.add_saveable('version', _save_version) + config.late_init(save_manager) log.init.debug("Initializing network...") networkmanager.init() @@ -419,10 +423,6 @@ def _init_modules(args, crash_handler): readline_bridge = readline.ReadlineBridge() objreg.register('readline-bridge', readline_bridge) - log.init.debug("Initializing config...") - config.init(qApp) - save_manager.init_autosave() - log.init.debug("Initializing sql...") try: sql.init(os.path.join(standarddir.data(), 'history.sqlite')) diff --git a/qutebrowser/config/config.py b/qutebrowser/config/config.py index 9907eb205..89eb4fad3 100644 --- a/qutebrowser/config/config.py +++ b/qutebrowser/config/config.py @@ -39,6 +39,8 @@ key_instance = None # Keeping track of all change filters to validate them later. _change_filters = [] +# Errors which happened during init, so we can show a message box. +_init_errors = [] class change_filter: # pylint: disable=invalid-name @@ -382,6 +384,14 @@ class Config(QObject): self._mutables = [] self._yaml = yaml_config + def init_save_manager(self, save_manager): + """Make sure the config gets saved properly. + + We do this outside of __init__ because the config gets created before + the save_manager exists. + """ + self._yaml.init_save_manager(save_manager) + def _set_value(self, opt, value): """Set the given option to the given value.""" if objects.backend is not None: @@ -634,18 +644,14 @@ class StyleSheetObserver(QObject): instance.changed.connect(self._update_stylesheet) -def init(parent=None): - """Initialize the config. - - Args: - parent: The parent to pass to QObjects which get initialized. - """ +def early_init(): + """Initialize the part of the config which works without a QApplication.""" configdata.init() yaml_config = configfiles.YamlConfig() global val, instance, key_instance - instance = Config(yaml_config=yaml_config, parent=parent) + instance = Config(yaml_config=yaml_config) val = ConfigContainer(instance) key_instance = KeyConfig(instance) @@ -666,12 +672,7 @@ def init(parent=None): raise configexc.ConfigFileErrors('config.py', config_api.errors) except configexc.ConfigFileErrors as e: log.config.exception("Error while loading config.py") - errbox = msgbox.msgbox(parent=None, - title="Error while reading config", - text=e.to_html(), - icon=QMessageBox.Warning, - plain_text=False) - errbox.exec_() + _init_errors.append(e) try: if getattr(config_api, 'load_autoconfig', True): @@ -683,12 +684,23 @@ def init(parent=None): desc = configexc.ConfigErrorDesc("Error", e) raise configexc.ConfigFileErrors('autoconfig.yml', [desc]) except configexc.ConfigFileErrors as e: - log.config.exception("Error while loading autoconfig.yml") + log.config.exception("Error while loading config.py") + _init_errors.append(e) + + configfiles.init() + + +def late_init(save_manager): + """Initialize the rest of the config after the QApplication is created.""" + global _init_errors + for err in _init_errors: errbox = msgbox.msgbox(parent=None, title="Error while reading config", - text=e.to_html(), + text=err.to_html(), icon=QMessageBox.Warning, plain_text=False) errbox.exec_() + _init_errors = [] - configfiles.init() + instance.init_save_manager(save_manager) + objreg.get('state-config').init_save_manager(save_manager) diff --git a/qutebrowser/config/configfiles.py b/qutebrowser/config/configfiles.py index 2bdb5d263..c9af13d95 100644 --- a/qutebrowser/config/configfiles.py +++ b/qutebrowser/config/configfiles.py @@ -39,7 +39,6 @@ class StateConfig(configparser.ConfigParser): def __init__(self): super().__init__() - save_manager = objreg.get('save-manager') self._filename = os.path.join(standarddir.data(), 'state') self.read(self._filename, encoding='utf-8') for sect in ['general', 'geometry']: @@ -49,6 +48,13 @@ class StateConfig(configparser.ConfigParser): pass # See commit a98060e020a4ba83b663813a4b9404edb47f28ad. self['general'].pop('fooled', None) + + def init_save_manager(self, save_manager): + """Make sure the config gets saved properly. + + We do this outside of __init__ because the config gets created before + the save_manager exists. + """ save_manager.add_saveable('state-config', self._save) def _save(self): @@ -68,12 +74,18 @@ class YamlConfig: VERSION = 1 def __init__(self): - save_manager = objreg.get('save-manager') self._filename = os.path.join(standarddir.config(auto=True), 'autoconfig.yml') - save_manager.add_saveable('yaml-config', self._save) self.values = {} + def init_save_manager(self, save_manager): + """Make sure the config gets saved properly. + + We do this outside of __init__ because the config gets created before + the save_manager exists. + """ + save_manager.add_saveable('yaml-config', self._save) + def _save(self): """Save the changed settings to the YAML file.""" data = {'config_version': self.VERSION, 'global': self.values} diff --git a/qutebrowser/misc/savemanager.py b/qutebrowser/misc/savemanager.py index 926bf1230..ddda5325b 100644 --- a/qutebrowser/misc/savemanager.py +++ b/qutebrowser/misc/savemanager.py @@ -113,19 +113,12 @@ class SaveManager(QObject): self.saveables = collections.OrderedDict() self._save_timer = usertypes.Timer(self, name='save-timer') self._save_timer.timeout.connect(self.autosave) + self._set_autosave_interval() + config.instance.changed.connect(self._set_autosave_interval) def __repr__(self): return utils.get_repr(self, saveables=self.saveables) - def init_autosave(self): - """Initialize auto-saving. - - We don't do this in __init__ because the config needs to be initialized - first, but the config needs the save manager. - """ - self._set_autosave_interval() - config.instance.changed.connect(self._set_autosave_interval) - @config.change_filter('auto_save.interval') def _set_autosave_interval(self): """Set the auto-save interval.""" diff --git a/tests/unit/config/test_config.py b/tests/unit/config/test_config.py index 4e4816e7c..c6e30f16d 100644 --- a/tests/unit/config/test_config.py +++ b/tests/unit/config/test_config.py @@ -873,6 +873,7 @@ def init_patch(qapp, fake_save_manager, monkeypatch, config_tmpdir, monkeypatch.setattr(config, 'instance', None) monkeypatch.setattr(config, 'key_instance', None) monkeypatch.setattr(config, '_change_filters', []) + monkeypatch.setattr(config, '_init_errors', []) yield for obj in ['config-commands', 'state-config']: try: @@ -885,8 +886,8 @@ def init_patch(qapp, fake_save_manager, monkeypatch, config_tmpdir, @pytest.mark.parametrize('config_py', [True, 'error', False]) @pytest.mark.parametrize('invalid_yaml', ['42', 'unknown', False]) # pylint: disable=too-many-branches -def test_init(init_patch, fake_save_manager, config_tmpdir, mocker, caplog, - load_autoconfig, config_py, invalid_yaml): +def test_early_init(init_patch, config_tmpdir, caplog, + load_autoconfig, config_py, invalid_yaml): # Prepare files autoconfig_file = config_tmpdir / 'autoconfig.yml' config_py_file = config_tmpdir / 'config.py' @@ -910,36 +911,32 @@ def test_init(init_patch, fake_save_manager, config_tmpdir, mocker, caplog, config_py_file.write_text('\n'.join(config_py_lines), 'utf-8', ensure=True) - msgbox_mock = mocker.patch('qutebrowser.config.config.msgbox.msgbox', - autospec=True) - with caplog.at_level(logging.ERROR): - config.init() + config.early_init() # Check error messages expected_errors = [] if config_py == 'error': - expected_errors.append("Errors occurred while reading config.py:") + expected_errors.append( + "Errors occurred while reading config.py:\n" + " While setting 'foo': No option 'foo'") if invalid_yaml and (load_autoconfig or not config_py): - expected_errors.append("Errors occurred while reading autoconfig.yml:") - if expected_errors: - assert len(expected_errors) == len(msgbox_mock.call_args_list) - comparisons = zip( - expected_errors, - [call[1]['text'] for call in msgbox_mock.call_args_list]) - for expected, actual in comparisons: - assert actual.strip().startswith(expected) - else: - assert not msgbox_mock.called + error = "Errors occurred while reading autoconfig.yml:\n" + if invalid_yaml == '42': + error += " While loading data: Toplevel object is not a dict" + elif invalid_yaml == 'unknown': + error += " Error: No option 'colors.foobar'" + else: + assert False, invalid_yaml + expected_errors.append(error) + + actual_errors = [str(err) for err in config._init_errors] + assert actual_errors == expected_errors # Make sure things have been init'ed objreg.get('config-commands') assert isinstance(config.instance, config.Config) assert isinstance(config.key_instance, config.KeyConfig) - fake_save_manager.add_saveable.assert_any_call( - 'state-config', unittest.mock.ANY) - fake_save_manager.add_saveable.assert_any_call( - 'yaml-config', unittest.mock.ANY) # Check config values if config_py and load_autoconfig and not invalid_yaml: @@ -955,7 +952,33 @@ def test_init(init_patch, fake_save_manager, config_tmpdir, mocker, caplog, assert config.instance._values == {'colors.hints.fg': 'magenta'} -def test_init_invalid_change_filter(init_patch): +def test_early_init_invalid_change_filter(init_patch): config.change_filter('foobar') with pytest.raises(configexc.NoOptionError): - config.init() + config.early_init() + + +@pytest.mark.parametrize('errors', [True, False]) +def test_late_init(init_patch, monkeypatch, fake_save_manager, mocker, errors): + config.early_init() + if errors: + err = configexc.ConfigErrorDesc("Error text", Exception("Exception")) + errs = configexc.ConfigFileErrors("config.py", [err]) + monkeypatch.setattr(config, '_init_errors', [errs]) + msgbox_mock = mocker.patch('qutebrowser.config.config.msgbox.msgbox', + autospec=True) + + config.late_init(fake_save_manager) + + fake_save_manager.add_saveable.assert_any_call( + 'state-config', unittest.mock.ANY) + fake_save_manager.add_saveable.assert_any_call( + 'yaml-config', unittest.mock.ANY) + if errors: + assert len(msgbox_mock.call_args_list) == 1 + _call_posargs, call_kwargs = msgbox_mock.call_args_list[0] + text = call_kwargs['text'].strip() + assert text.startswith('Errors occurred while reading config.py:') + assert 'Error text: Exception' in text + else: + assert not msgbox_mock.called From f40103cbba6a5dff20ffccb7a6e7949b5c6f9faa Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sun, 17 Sep 2017 20:38:34 +0200 Subject: [PATCH 02/50] Don't require qapp for configtypes tests anymore We need to make sure they work without a QApplication, and the only reason they needed one before was standarddir. --- tests/unit/config/test_configtypes.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/unit/config/test_configtypes.py b/tests/unit/config/test_configtypes.py index d9845ba03..861c052c2 100644 --- a/tests/unit/config/test_configtypes.py +++ b/tests/unit/config/test_configtypes.py @@ -214,7 +214,7 @@ class TestAll: def klass(self, request): return request.param - @pytest.mark.usefixtures('qapp', 'config_tmpdir') + @pytest.mark.usefixtures('config_tmpdir') @hypothesis.given(strategies.text()) @hypothesis.example('\x00') def test_from_str_hypothesis(self, klass, s): @@ -1561,7 +1561,6 @@ def unrequired_class(**kwargs): return configtypes.File(required=False, **kwargs) -@pytest.mark.usefixtures('qapp') @pytest.mark.usefixtures('config_tmpdir') class TestFile: From 70b8585e9505cc6a9815c6c811684c756863cbf4 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sun, 17 Sep 2017 20:44:08 +0200 Subject: [PATCH 03/50] Move qtutils.unset_organization to standarddir --- qutebrowser/utils/qtutils.py | 18 ------------------ qutebrowser/utils/standarddir.py | 23 +++++++++++++++++++++-- tests/unit/utils/test_qtutils.py | 15 --------------- tests/unit/utils/test_standarddir.py | 22 ++++++++++++++++++++++ 4 files changed, 43 insertions(+), 35 deletions(-) diff --git a/qutebrowser/utils/qtutils.py b/qutebrowser/utils/qtutils.py index 6778c30ee..ad02c9a45 100644 --- a/qutebrowser/utils/qtutils.py +++ b/qutebrowser/utils/qtutils.py @@ -35,7 +35,6 @@ import contextlib from PyQt5.QtCore import (qVersion, QEventLoop, QDataStream, QByteArray, QIODevice, QSaveFile, QT_VERSION_STR) -from PyQt5.QtWidgets import QApplication try: from PyQt5.QtWebKit import qWebKitVersion except ImportError: # pragma: no cover @@ -242,23 +241,6 @@ def savefile_open(filename, binary=False, encoding='utf-8'): raise QtOSError(f, msg="Commit failed!") -@contextlib.contextmanager -def unset_organization(): - """Temporarily unset QApplication.organizationName(). - - This is primarily needed in config.py. - """ - qapp = QApplication.instance() - if qapp is not None: - orgname = qapp.organizationName() - qapp.setOrganizationName(None) - try: - yield - finally: - if qapp is not None: - qapp.setOrganizationName(orgname) - - class PyQIODevice(io.BufferedIOBase): """Wrapper for a QIODevice which provides a python interface. diff --git a/qutebrowser/utils/standarddir.py b/qutebrowser/utils/standarddir.py index a4f4dbd7b..468fba51e 100644 --- a/qutebrowser/utils/standarddir.py +++ b/qutebrowser/utils/standarddir.py @@ -23,10 +23,12 @@ import os import sys import shutil import os.path +import contextlib from PyQt5.QtCore import QStandardPaths +from PyQt5.QtWidgets import QApplication -from qutebrowser.utils import log, qtutils, debug, usertypes, message +from qutebrowser.utils import log, debug, usertypes, message # The cached locations _locations = {} @@ -45,6 +47,23 @@ class EmptyValueError(Exception): """Error raised when QStandardPaths returns an empty value.""" +@contextlib.contextmanager +def _unset_organization(): + """Temporarily unset QApplication.organizationName(). + + This is primarily needed in config.py. + """ + qapp = QApplication.instance() + if qapp is not None: + orgname = qapp.organizationName() + qapp.setOrganizationName(None) + try: + yield + finally: + if qapp is not None: + qapp.setOrganizationName(orgname) + + def _init_config(args): """Initialize the location for configs.""" typ = QStandardPaths.ConfigLocation @@ -204,7 +223,7 @@ def _writable_location(typ): # FIXME old Qt getattr(QStandardPaths, 'AppDataLocation', object())], typ_str - with qtutils.unset_organization(): + with _unset_organization(): path = QStandardPaths.writableLocation(typ) log.misc.debug("writable location for {}: {}".format(typ_str, path)) diff --git a/tests/unit/utils/test_qtutils.py b/tests/unit/utils/test_qtutils.py index ff8b81c9a..a87b9c745 100644 --- a/tests/unit/utils/test_qtutils.py +++ b/tests/unit/utils/test_qtutils.py @@ -530,21 +530,6 @@ class TestSavefileOpen: assert data == b'foo\nbar\nbaz' -@pytest.mark.parametrize('orgname, expected', [(None, ''), ('test', 'test')]) -def test_unset_organization(qapp, orgname, expected): - """Test unset_organization. - - Args: - orgname: The organizationName to set initially. - expected: The organizationName which is expected when reading back. - """ - qapp.setOrganizationName(orgname) - assert qapp.organizationName() == expected # sanity check - with qtutils.unset_organization(): - assert qapp.organizationName() == '' - assert qapp.organizationName() == expected - - if test_file is not None and sys.platform != 'darwin': # If we were able to import Python's test_file module, we run some code # here which defines unittest TestCases to run the python tests over diff --git a/tests/unit/utils/test_standarddir.py b/tests/unit/utils/test_standarddir.py index 011e86b42..b828926e9 100644 --- a/tests/unit/utils/test_standarddir.py +++ b/tests/unit/utils/test_standarddir.py @@ -56,6 +56,28 @@ def clear_standarddir_cache_and_patch(qapp, monkeypatch): monkeypatch.setattr(standarddir, '_locations', {}) +@pytest.mark.parametrize('orgname, expected', [(None, ''), ('test', 'test')]) +def test_unset_organization(qapp, orgname, expected): + """Test unset_organization. + + Args: + orgname: The organizationName to set initially. + expected: The organizationName which is expected when reading back. + """ + qapp.setOrganizationName(orgname) + assert qapp.organizationName() == expected # sanity check + with standarddir._unset_organization(): + assert qapp.organizationName() == '' + assert qapp.organizationName() == expected + + +def test_unset_organization_no_qapp(monkeypatch): + """Without a QApplication, _unset_organization should do nothing.""" + monkeypatch.setattr(standarddir.QApplication, 'instance', lambda: None) + with standarddir._unset_organization(): + pass + + def test_fake_mac_config(tmpdir, monkeypatch): """Test standardir.config on a fake Mac.""" monkeypatch.setattr(sys, 'platform', 'darwin') From 3e0ca5d94dbc4a803b7d3c99799d078b57b4c258 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sun, 17 Sep 2017 21:04:34 +0200 Subject: [PATCH 04/50] Stop using objreg for state-config --- qutebrowser/app.py | 18 ++++-------------- qutebrowser/browser/inspector.py | 9 ++++----- qutebrowser/config/config.py | 2 +- qutebrowser/config/configfiles.py | 8 +++++++- qutebrowser/mainwindow/mainwindow.py | 8 +++----- qutebrowser/misc/crashdialog.py | 11 ++++++----- qutebrowser/misc/sessions.py | 5 ++--- tests/unit/config/test_config.py | 12 ++++++------ tests/unit/config/test_configfiles.py | 4 ++-- tests/unit/misc/test_sessions.py | 7 +++---- 10 files changed, 38 insertions(+), 46 deletions(-) diff --git a/qutebrowser/app.py b/qutebrowser/app.py index 3f24af721..f8cf49723 100644 --- a/qutebrowser/app.py +++ b/qutebrowser/app.py @@ -43,7 +43,7 @@ import qutebrowser import qutebrowser.resources from qutebrowser.completion.models import miscmodels from qutebrowser.commands import cmdutils, runners, cmdexc -from qutebrowser.config import config, websettings, configexc +from qutebrowser.config import config, websettings, configexc, configfiles from qutebrowser.browser import (urlmarks, adblock, history, browsertab, downloads) from qutebrowser.browser.network import proxy @@ -213,13 +213,12 @@ def _load_session(name): Args: name: The name of the session to load, or None to read state file. """ - state_config = objreg.get('state-config') session_manager = objreg.get('session-manager') if name is None and session_manager.exists('_autosave'): name = '_autosave' elif name is None: try: - name = state_config['general']['session'] + name = configfiles.state['general']['session'] except KeyError: # No session given as argument and none in the session file -> # start without loading a session @@ -232,7 +231,7 @@ def _load_session(name): except sessions.SessionError as e: message.error("Failed to load session {}: {}".format(name, e)) try: - del state_config['general']['session'] + del configfiles.state['general']['session'] except KeyError: pass # If this was a _restart session, delete it. @@ -326,8 +325,7 @@ def _open_special_pages(args): # With --basedir given, don't open anything. return - state_config = objreg.get('state-config') - general_sect = state_config['general'] + general_sect = configfiles.state['general'] tabbed_browser = objreg.get('tabbed-browser', scope='window', window='last-focused') @@ -363,13 +361,6 @@ def _open_special_pages(args): general_sect['config-migration-shown'] = '1' -def _save_version(): - """Save the current version to the state config.""" - state_config = objreg.get('state-config', None) - if state_config is not None: - state_config['general']['version'] = qutebrowser.__version__ - - def on_focus_changed(_old, new): """Register currently focused main window in the object registry.""" if new is None: @@ -410,7 +401,6 @@ def _init_modules(args, crash_handler): log.init.debug("Initializing save manager...") save_manager = savemanager.SaveManager(qApp) objreg.register('save-manager', save_manager) - save_manager.add_saveable('version', _save_version) config.late_init(save_manager) log.init.debug("Initializing network...") diff --git a/qutebrowser/browser/inspector.py b/qutebrowser/browser/inspector.py index e225c31da..c88242bb8 100644 --- a/qutebrowser/browser/inspector.py +++ b/qutebrowser/browser/inspector.py @@ -24,7 +24,8 @@ import binascii from PyQt5.QtWidgets import QWidget -from qutebrowser.utils import log, objreg, usertypes +from qutebrowser.config import configfiles +from qutebrowser.utils import log, usertypes from qutebrowser.misc import miscwidgets, objects @@ -67,9 +68,8 @@ class AbstractWebInspector(QWidget): def _load_state_geometry(self): """Load the geometry from the state file.""" - state_config = objreg.get('state-config') try: - data = state_config['geometry']['inspector'] + data = configfiles.state['geometry']['inspector'] geom = base64.b64decode(data, validate=True) except KeyError: # First start @@ -84,10 +84,9 @@ class AbstractWebInspector(QWidget): def closeEvent(self, e): """Save the geometry when closed.""" - state_config = objreg.get('state-config') data = bytes(self.saveGeometry()) geom = base64.b64encode(data).decode('ASCII') - state_config['geometry']['inspector'] = geom + configfiles.state['geometry']['inspector'] = geom super().closeEvent(e) def inspect(self, page): diff --git a/qutebrowser/config/config.py b/qutebrowser/config/config.py index 89eb4fad3..3047b79b7 100644 --- a/qutebrowser/config/config.py +++ b/qutebrowser/config/config.py @@ -703,4 +703,4 @@ def late_init(save_manager): _init_errors = [] instance.init_save_manager(save_manager) - objreg.get('state-config').init_save_manager(save_manager) + configfiles.state.init_save_manager(save_manager) diff --git a/qutebrowser/config/configfiles.py b/qutebrowser/config/configfiles.py index c9af13d95..878e49443 100644 --- a/qutebrowser/config/configfiles.py +++ b/qutebrowser/config/configfiles.py @@ -29,10 +29,15 @@ import contextlib import yaml from PyQt5.QtCore import QSettings +import qutebrowser from qutebrowser.config import configexc from qutebrowser.utils import objreg, standarddir, utils, qtutils +# The StateConfig instance +state = None + + class StateConfig(configparser.ConfigParser): """The "state" file saving various application state.""" @@ -232,8 +237,9 @@ def read_config_py(filename=None): def init(): """Initialize config storage not related to the main config.""" + global state state = StateConfig() - objreg.register('state-config', state) + state['general']['version'] = qutebrowser.__version__ # Set the QSettings path to something like # ~/.config/qutebrowser/qsettings/qutebrowser/qutebrowser.conf so it diff --git a/qutebrowser/mainwindow/mainwindow.py b/qutebrowser/mainwindow/mainwindow.py index 77ed03b36..04ae464b3 100644 --- a/qutebrowser/mainwindow/mainwindow.py +++ b/qutebrowser/mainwindow/mainwindow.py @@ -28,7 +28,7 @@ from PyQt5.QtCore import pyqtSlot, QRect, QPoint, QTimer, Qt from PyQt5.QtWidgets import QWidget, QVBoxLayout, QApplication, QSizePolicy from qutebrowser.commands import runners, cmdutils -from qutebrowser.config import config +from qutebrowser.config import config, configfiles from qutebrowser.utils import (message, log, usertypes, qtutils, objreg, utils, jinja, debug) from qutebrowser.mainwindow import tabbedbrowser, messageview, prompt @@ -365,9 +365,8 @@ class MainWindow(QWidget): def _load_state_geometry(self): """Load the geometry from the state file.""" - state_config = objreg.get('state-config') try: - data = state_config['geometry']['mainwindow'] + data = configfiles.state['geometry']['mainwindow'] geom = base64.b64decode(data, validate=True) except KeyError: # First start @@ -380,10 +379,9 @@ class MainWindow(QWidget): def _save_geometry(self): """Save the window geometry to the state config.""" - state_config = objreg.get('state-config') data = bytes(self.saveGeometry()) geom = base64.b64encode(data).decode('ASCII') - state_config['geometry']['mainwindow'] = geom + configfiles.state['geometry']['mainwindow'] = geom def _load_geometry(self, geom): """Load geometry from a bytes object. diff --git a/qutebrowser/misc/crashdialog.py b/qutebrowser/misc/crashdialog.py index 5b348ca0c..4d64faabc 100644 --- a/qutebrowser/misc/crashdialog.py +++ b/qutebrowser/misc/crashdialog.py @@ -38,7 +38,7 @@ import qutebrowser from qutebrowser.utils import version, log, utils, objreg, usertypes from qutebrowser.misc import (miscwidgets, autoupdate, msgbox, httpclient, pastebin, objects) -from qutebrowser.config import config +from qutebrowser.config import config, configfiles def parse_fatal_stacktrace(text): @@ -159,11 +159,12 @@ class _CrashDialog(QDialog): self._vbox.addWidget(contact) self._contact = QTextEdit(tabChangesFocus=True, acceptRichText=False) try: - state = objreg.get('state-config') try: - self._contact.setPlainText(state['general']['contact-info']) + info = configfiles.state['general']['contact-info'] except KeyError: self._contact.setPlaceholderText("Mail or IRC nickname") + else: + self._contact.setPlainText(info) except Exception: log.misc.exception("Failed to get contact information!") self._contact.setPlaceholderText("Mail or IRC nickname") @@ -296,8 +297,8 @@ class _CrashDialog(QDialog): def _save_contact_info(self): """Save the contact info to disk.""" try: - state = objreg.get('state-config') - state['general']['contact-info'] = self._contact.toPlainText() + info = self._contact.toPlainText() + configfiles.state['general']['contact-info'] = info except Exception: log.misc.exception("Failed to save contact information!") diff --git a/qutebrowser/misc/sessions.py b/qutebrowser/misc/sessions.py index 8d225c654..c4a2ad661 100644 --- a/qutebrowser/misc/sessions.py +++ b/qutebrowser/misc/sessions.py @@ -30,7 +30,7 @@ import yaml from qutebrowser.utils import (standarddir, objreg, qtutils, log, message, utils) from qutebrowser.commands import cmdexc, cmdutils -from qutebrowser.config import config +from qutebrowser.config import config, configfiles from qutebrowser.completion.models import miscmodels @@ -294,8 +294,7 @@ class SessionManager(QObject): raise SessionError(e) if load_next_time: - state_config = objreg.get('state-config') - state_config['general']['session'] = name + configfiles.state['general']['session'] = name return name def save_autosave(self): diff --git a/tests/unit/config/test_config.py b/tests/unit/config/test_config.py index c6e30f16d..64d36082f 100644 --- a/tests/unit/config/test_config.py +++ b/tests/unit/config/test_config.py @@ -28,7 +28,7 @@ from PyQt5.QtCore import QObject, QUrl from PyQt5.QtGui import QColor from qutebrowser.commands import cmdexc -from qutebrowser.config import config, configdata, configexc +from qutebrowser.config import config, configdata, configexc, configfiles from qutebrowser.utils import objreg, usertypes from qutebrowser.misc import objects @@ -870,16 +870,16 @@ def test_set_register_stylesheet(delete, stylesheet_param, update, qtbot, def init_patch(qapp, fake_save_manager, monkeypatch, config_tmpdir, data_tmpdir): monkeypatch.setattr(configdata, 'DATA', None) + monkeypatch.setattr(configfiles, 'state', None) monkeypatch.setattr(config, 'instance', None) monkeypatch.setattr(config, 'key_instance', None) monkeypatch.setattr(config, '_change_filters', []) monkeypatch.setattr(config, '_init_errors', []) yield - for obj in ['config-commands', 'state-config']: - try: - objreg.delete(obj) - except KeyError: - pass + try: + objreg.delete('config-commands') + except KeyError: + pass @pytest.mark.parametrize('load_autoconfig', [True, False]) # noqa diff --git a/tests/unit/config/test_configfiles.py b/tests/unit/config/test_configfiles.py index c10b0c4b4..2732585a3 100644 --- a/tests/unit/config/test_configfiles.py +++ b/tests/unit/config/test_configfiles.py @@ -320,9 +320,9 @@ class TestConfigPy: @pytest.fixture def init_patch(qapp, fake_save_manager, config_tmpdir, data_tmpdir, - config_stub): + config_stub, monkeypatch): + monkeypatch.setattr(configfiles, 'state', None) yield - objreg.delete('state-config') def test_init(init_patch, config_tmpdir): diff --git a/tests/unit/misc/test_sessions.py b/tests/unit/misc/test_sessions.py index db9bef949..3a387d0dc 100644 --- a/tests/unit/misc/test_sessions.py +++ b/tests/unit/misc/test_sessions.py @@ -186,11 +186,10 @@ def test_get_session_name(config_stub, sess_man, arg, config, current, class TestSave: @pytest.fixture - def state_config(self): + def state_config(self, monkeypatch): state = {'general': {}} - objreg.register('state-config', state) - yield state - objreg.delete('state-config') + monkeypatch.setattr(sessions.configfiles, 'state', state) + return state @pytest.fixture def fake_history(self, win_registry, stubs, monkeypatch, webview): From 4c616a5733dfa98a0d1b557d679af255ddb9759b Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sun, 17 Sep 2017 21:23:36 +0200 Subject: [PATCH 05/50] Move all documentation files to doc/ --- .github/CONTRIBUTING.asciidoc | 1 + MANIFEST.in | 2 +- README.asciidoc | 18 +++++++++--------- COPYING => doc/COPYING | 0 CHANGELOG.asciidoc => doc/changelog.asciidoc | 0 .../contributing.asciidoc | 0 FAQ.asciidoc => doc/faq.asciidoc | 2 +- doc/help/index.asciidoc | 6 +++--- INSTALL.asciidoc => doc/install.asciidoc | 0 misc/qutebrowser.nsi | 2 +- qutebrowser/html/backend-warning.html | 2 +- qutebrowser/utils/docutils.py | 2 +- scripts/asciidoc2html.py | 12 ++++-------- scripts/dev/Makefile-dmg | 2 +- .../data/downloads/mhtml/complex/complex.html | 2 +- .../data/downloads/mhtml/complex/complex.mht | 2 +- www/header.asciidoc | 8 ++++---- 17 files changed, 29 insertions(+), 32 deletions(-) create mode 120000 .github/CONTRIBUTING.asciidoc rename COPYING => doc/COPYING (100%) rename CHANGELOG.asciidoc => doc/changelog.asciidoc (100%) rename CONTRIBUTING.asciidoc => doc/contributing.asciidoc (100%) rename FAQ.asciidoc => doc/faq.asciidoc (99%) rename INSTALL.asciidoc => doc/install.asciidoc (100%) diff --git a/.github/CONTRIBUTING.asciidoc b/.github/CONTRIBUTING.asciidoc new file mode 120000 index 000000000..93e1056d8 --- /dev/null +++ b/.github/CONTRIBUTING.asciidoc @@ -0,0 +1 @@ +../doc/contributing.asciidoc \ No newline at end of file diff --git a/MANIFEST.in b/MANIFEST.in index 52beeab1e..f3cd7d5a0 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -11,7 +11,7 @@ graft misc/userscripts recursive-include scripts *.py *.sh include qutebrowser/utils/testfile include qutebrowser/git-commit-id -include COPYING doc/* README.asciidoc CONTRIBUTING.asciidoc FAQ.asciidoc INSTALL.asciidoc CHANGELOG.asciidoc +include doc/* include qutebrowser.desktop include requirements.txt include tox.ini diff --git a/README.asciidoc b/README.asciidoc index c7f3ebc48..1bb2feba8 100644 --- a/README.asciidoc +++ b/README.asciidoc @@ -9,7 +9,7 @@ qutebrowser // QUTE_WEB_HIDE image:icons/qutebrowser-64x64.png[qutebrowser logo] *A keyboard-driven, vim-like browser based on PyQt5 and Qt.* -image:https://img.shields.io/pypi/l/qutebrowser.svg?style=flat["license badge",link="https://github.com/qutebrowser/qutebrowser/blob/master/COPYING"] +image:https://img.shields.io/pypi/l/qutebrowser.svg?style=flat["license badge",link="https://github.com/qutebrowser/qutebrowser/blob/master/doc/COPYING"] image:https://img.shields.io/pypi/v/qutebrowser.svg?style=flat["version badge",link="https://pypi.python.org/pypi/qutebrowser/"] image:https://requires.io/github/qutebrowser/qutebrowser/requirements.svg?branch=master["requirements badge",link="https://requires.io/github/qutebrowser/qutebrowser/requirements/?branch=master"] image:https://travis-ci.org/qutebrowser/qutebrowser.svg?branch=master["Build Status", link="https://travis-ci.org/qutebrowser/qutebrowser"] @@ -36,7 +36,7 @@ Downloads --------- See the https://github.com/qutebrowser/qutebrowser/releases[github releases -page] for available downloads and the link:INSTALL.asciidoc[INSTALL] file for +page] for available downloads and the link:doc/install.asciidoc[INSTALL] file for detailed instructions on how to get qutebrowser running on various platforms. Documentation @@ -49,11 +49,11 @@ available: image:https://qutebrowser.org/img/cheatsheet-small.png["qutebrowser key binding cheatsheet",link="https://qutebrowser.org/img/cheatsheet-big.png"] * link:doc/quickstart.asciidoc[Quick start guide] * A https://www.shortcutfoo.com/app/dojos/qutebrowser[free training course] to remember those key bindings. -* link:FAQ.asciidoc[Frequently asked questions] +* link:doc/faq.asciidoc[Frequently asked questions] * link:doc/help/configuring.html[Configuring qutebrowser] -* link:CONTRIBUTING.asciidoc[Contributing to qutebrowser] -* link:INSTALL.asciidoc[INSTALL] -* link:CHANGELOG.asciidoc[Change Log] +* link:doc/contributing.asciidoc[Contributing to qutebrowser] +* link:doc/install.asciidoc[Installing qutebrowser] +* link:doc/changelog.asciidoc[Change Log] * link:doc/stacktrace.asciidoc[Reporting segfaults] * link:doc/userscripts.asciidoc[How to write userscripts] @@ -79,7 +79,7 @@ Contributions / Bugs -------------------- You want to contribute to qutebrowser? Awesome! Please read -link:CONTRIBUTING.asciidoc[the contribution guidelines] for details and +link:doc/contributing.asciidoc[the contribution guidelines] for details and useful hints. If you found a bug or have a feature request, you can report it in several @@ -129,8 +129,8 @@ The following libraries are optional: * http://asciidoc.org/[asciidoc] to generate the documentation for the `:help` command, when using the git repository (rather than a release). -See link:INSTALL.asciidoc[INSTALL] for directions on how to install qutebrowser -and its dependencies. +See link:doc/install.asciidoc[the documentation] for directions on how to +install qutebrowser and its dependencies. Donating -------- diff --git a/COPYING b/doc/COPYING similarity index 100% rename from COPYING rename to doc/COPYING diff --git a/CHANGELOG.asciidoc b/doc/changelog.asciidoc similarity index 100% rename from CHANGELOG.asciidoc rename to doc/changelog.asciidoc diff --git a/CONTRIBUTING.asciidoc b/doc/contributing.asciidoc similarity index 100% rename from CONTRIBUTING.asciidoc rename to doc/contributing.asciidoc diff --git a/FAQ.asciidoc b/doc/faq.asciidoc similarity index 99% rename from FAQ.asciidoc rename to doc/faq.asciidoc index 0fe340474..cfc16ec12 100644 --- a/FAQ.asciidoc +++ b/doc/faq.asciidoc @@ -216,7 +216,7 @@ Segfaults on Facebook, Medium, Amazon, ...:: visiting these sites. This is caused by various bugs in Qt which have been fixed in Qt 5.4. However Debian and Ubuntu are slow to adopt or upgrade some packages. On Debian Jessie, it's recommended to use the experimental - repos as described in https://github.com/qutebrowser/qutebrowser/blob/master/INSTALL.asciidoc#on-debian--ubuntu[INSTALL]. + repos as described in https://github.com/qutebrowser/qutebrowser/blob/master/doc/install.asciidoc#on-debian--ubuntu[the documentation]. + Since Ubuntu Trusty (using Qt 5.2.1), https://bugreports.qt.io/browse/QTBUG-42417?jql=component%20%3D%20WebKit%20and%20resolution%20%3D%20Done%20and%20fixVersion%20in%20(5.3.0%2C%20%225.3.0%20Alpha%22%2C%20%225.3.0%20Beta1%22%2C%20%225.3.0%20RC1%22%2C%205.3.1%2C%205.3.2%2C%205.4.0%2C%20%225.4.0%20Alpha%22%2C%20%225.4.0%20Beta%22%2C%20%225.4.0%20RC%22)%20and%20priority%20in%20(%22P2%3A%20Important%22%2C%20%22P1%3A%20Critical%22%2C%20%22P0%3A%20Blocker%22)[over diff --git a/doc/help/index.asciidoc b/doc/help/index.asciidoc index e8321f2ad..4edea719e 100644 --- a/doc/help/index.asciidoc +++ b/doc/help/index.asciidoc @@ -7,13 +7,13 @@ Documentation The following help pages are currently available: * link:../quickstart.html[Quick start guide] -* link:../../FAQ.html[Frequently asked questions] -* link:../../CHANGELOG.html[Change Log] +* link:../doc.html[Frequently asked questions] +* link:../changelog.html[Change Log] * link:commands.html[Documentation of commands] * link:configuring.html[Configuring qutebrowser] * link:settings.html[Documentation of settings] * link:../userscripts.html[How to write userscripts] -* link:../../CONTRIBUTING.html[Contributing to qutebrowser] +* link:../contributing.html[Contributing to qutebrowser] Getting help ------------ diff --git a/INSTALL.asciidoc b/doc/install.asciidoc similarity index 100% rename from INSTALL.asciidoc rename to doc/install.asciidoc diff --git a/misc/qutebrowser.nsi b/misc/qutebrowser.nsi index 8f815c391..bc0b90b1b 100644 --- a/misc/qutebrowser.nsi +++ b/misc/qutebrowser.nsi @@ -23,7 +23,7 @@ SetCompressor /solid lzma !define MUI_ICON "../icons/qutebrowser.ico" !define MUI_UNICON "../icons/qutebrowser.ico" -!insertmacro MUI_PAGE_LICENSE "..\COPYING" +!insertmacro MUI_PAGE_LICENSE "..\doc\COPYING" !insertmacro MUI_PAGE_DIRECTORY !insertmacro MUI_PAGE_INSTFILES !insertmacro MUI_UNPAGE_CONFIRM diff --git a/qutebrowser/html/backend-warning.html b/qutebrowser/html/backend-warning.html index 2b631d6a5..0ba8e95ee 100644 --- a/qutebrowser/html/backend-warning.html +++ b/qutebrowser/html/backend-warning.html @@ -50,7 +50,7 @@ the qute://settings page or caret browsing). {% elif distribution.version >= version('17.04') %} {{ install_webengine('python3-pyqt5.qtwebengine') }} {% elif distribution.version >= version('16.04') %} - QtWebEngine is only available in Ubuntu's repositories since 17.04, but you can install qutebrowser via tox with tox -e mkvenv-pypi to use the new backend. + QtWebEngine is only available in Ubuntu's repositories since 17.04, but you can install qutebrowser via tox with tox -e mkvenv-pypi to use the new backend. {% else %} Unfortunately, no easy way is known to install QtWebEngine on Ubuntu < 16.04. {{ please_open_issue() }} {% endif %} diff --git a/qutebrowser/utils/docutils.py b/qutebrowser/utils/docutils.py index 1991068f9..567451e05 100644 --- a/qutebrowser/utils/docutils.py +++ b/qutebrowser/utils/docutils.py @@ -63,7 +63,7 @@ class DocstringParser: """Generate documentation based on a docstring of a command handler. - The docstring needs to follow the format described in CONTRIBUTING. + The docstring needs to follow the format described in doc/contributing. Attributes: _state: The current state of the parser state machine. diff --git a/scripts/asciidoc2html.py b/scripts/asciidoc2html.py index 6c7fdaf6e..dbf03e2f6 100755 --- a/scripts/asciidoc2html.py +++ b/scripts/asciidoc2html.py @@ -40,13 +40,7 @@ class AsciiDoc: """Abstraction of an asciidoc subprocess.""" - FILES = [ - ('FAQ.asciidoc', 'qutebrowser/html/doc/FAQ.html'), - ('CHANGELOG.asciidoc', 'qutebrowser/html/doc/CHANGELOG.html'), - ('CONTRIBUTING.asciidoc', 'qutebrowser/html/doc/CONTRIBUTING.html'), - ('doc/quickstart.asciidoc', 'qutebrowser/html/doc/quickstart.html'), - ('doc/userscripts.asciidoc', 'qutebrowser/html/doc/userscripts.html'), - ] + FILES = ['faq', 'changelog', 'contributing', 'quickstart', 'userscripts'] def __init__(self, args): self._cmd = None @@ -80,7 +74,9 @@ class AsciiDoc: def _build_docs(self): """Render .asciidoc files to .html sites.""" - files = self.FILES[:] + files = [('doc/{}.asciidoc'.format(f), + 'qutebrowser/html/{}.html'.format(f)) + for f in self.FILES] for src in glob.glob('doc/help/*.asciidoc'): name, _ext = os.path.splitext(os.path.basename(src)) dst = 'qutebrowser/html/doc/{}.html'.format(name) diff --git a/scripts/dev/Makefile-dmg b/scripts/dev/Makefile-dmg index 0f699ea8b..e4711a038 100644 --- a/scripts/dev/Makefile-dmg +++ b/scripts/dev/Makefile-dmg @@ -21,7 +21,7 @@ NAME ?= qutebrowser SOURCE_DIR ?= . -SOURCE_FILES ?= dist/qutebrowser.app COPYING +SOURCE_FILES ?= dist/qutebrowser.app doc/COPYING TEMPLATE_DMG ?= template.dmg TEMPLATE_SIZE ?= 300m diff --git a/tests/end2end/data/downloads/mhtml/complex/complex.html b/tests/end2end/data/downloads/mhtml/complex/complex.html index 4b34d3fed..b298aa37c 100644 --- a/tests/end2end/data/downloads/mhtml/complex/complex.html +++ b/tests/end2end/data/downloads/mhtml/complex/complex.html @@ -80,7 +80,7 @@

...and how?

-

See +

See here for more information.

More useless trivia!

diff --git a/tests/end2end/data/downloads/mhtml/complex/complex.mht b/tests/end2end/data/downloads/mhtml/complex/complex.mht index 0cbcc4607..0467da22f 100644 --- a/tests/end2end/data/downloads/mhtml/complex/complex.mht +++ b/tests/end2end/data/downloads/mhtml/complex/complex.mht @@ -97,7 +97,7 @@ the

...and how?

=20

See +aster/doc/contributing.asciidoc"> here for more information.

=20

More useless trivia!

diff --git a/www/header.asciidoc b/www/header.asciidoc index b463de7d2..8d9b5ac21 100644 --- a/www/header.asciidoc +++ b/www/header.asciidoc @@ -9,10 +9,10 @@