From c2197102a38c9eee128d57db406682b51448b1e0 Mon Sep 17 00:00:00 2001 From: Michal Siedlaczek Date: Sun, 6 Aug 2017 16:10:12 -0700 Subject: [PATCH 01/20] Enable spell checking and installing dictionaries for QtWebEngine --- MANIFEST.in | 1 + misc/lang_list | 42 ++++++ qutebrowser/browser/webengine/spell.py | 142 ++++++++++++++++++ .../browser/webengine/webenginesettings.py | 20 +++ scripts/install_dict.py | 66 ++++++++ tests/unit/browser/webengine/test_spell.py | 69 +++++++++ 6 files changed, 340 insertions(+) create mode 100644 misc/lang_list create mode 100644 qutebrowser/browser/webengine/spell.py create mode 100755 scripts/install_dict.py create mode 100644 tests/unit/browser/webengine/test_spell.py diff --git a/MANIFEST.in b/MANIFEST.in index ec906aaf4..d6f9e713a 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -18,6 +18,7 @@ include tox.ini include qutebrowser.py include misc/cheatsheet.svg include qutebrowser/config/configdata.yml +include misc/lang_list prune www prune scripts/dev diff --git a/misc/lang_list b/misc/lang_list new file mode 100644 index 000000000..ea5a1b17b --- /dev/null +++ b/misc/lang_list @@ -0,0 +1,42 @@ +af-ZA Afrikaans (South Africa) af-ZA-3-0.bdic +bg-BG Bulgarian (Bulgaria) bg-BG-3-0.bdic +ca-ES Catalan (Spain) ca-ES-3-0.bdic +cs-CZ Czech (Czech Republic) cs-CZ-3-0.bdic +da-DK Danish (Denmark) da-DK-3-0.bdic +de-DE German (Germany) de-DE-3-0.bdic +el-GR Greek (Greece) el-GR-3-0.bdic +en-CA English (Canada) en-CA-7-1.bdic +en-GB English (United Kingdom) en-GB-7-1.bdic +en-US English (United States) en-US-7-1.bdic +es-ES Spanish (Spain) es-ES-3-0.bdic +et-EE Estonian (Estonia) et-EE-3-0.bdic +fa-IR Farsi (Iran) fa-IR-7-0.bdic +fo-FO Faroese (Faroe Islands) fo-FO-3-0.bdic +fr-FR French (France) fr-FR-3-0.bdic +he-IL Hebrew (Israel) he-IL-3-0.bdic +hi-IN Hindi (India) hi-IN-3-0.bdic +hr-HR Croatian (Croatia) hr-HR-3-0.bdic +hu-HU Hungarian (Hungary) hu-HU-3-0.bdic +id-ID Indonesian (Indonesia) id-ID-3-0.bdic +it-IT Italian (Italy) it-IT-3-0.bdic +ko Korean ko-3-0.bdic +lt-LT Lithuanian (Lithuania) lt-LT-3-0.bdic +lv-LV Latvian (Latvia) lv-LV-3-0.bdic +nb-NO Norwegian (Norway) nb-NO-3-0.bdic +nl-NL Dutch (Netherlands) nl-NL-3-0.bdic +pl-PL Polish (Poland) pl-PL-3-0.bdic +pt-BR Portuguese (Brazil) pt-BR-3-0.bdic +pt-PT Portuguese (Portugal) pt-PT-3-0.bdic +ro-RO Romanian (Romania) ro-RO-3-0.bdic +ru-RU Russian (Russia) ru-RU-3-0.bdic +sh Serbo-Croatian sh-3-0.bdic +sk-SK Slovak (Slovakia) sk-SK-3-0.bdic +sl-SI Slovenian (Slovenia) sl-SI-3-0.bdic +sq Albanian sq-3-0.bdic +sr Serbian sr-3-0.bdic +sv-SE Swedish (Sweden) sv-SE-3-0.bdic +ta-IN Tamil (India) ta-IN-3-0.bdic +tg-TG Tajik (Tajikistan) tg-TG-5-0.bdic +tr-TR Turkish (Turkey) tr-TR-4-0.bdic +uk-UA Ukrainian (Ukraine) uk-UA-3-0.bdic +vi-VN Vietnamese (Viet Nam) vi-VN-3-0.bdic diff --git a/qutebrowser/browser/webengine/spell.py b/qutebrowser/browser/webengine/spell.py new file mode 100644 index 000000000..5a5df344b --- /dev/null +++ b/qutebrowser/browser/webengine/spell.py @@ -0,0 +1,142 @@ +# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et + +# Copyright 2017 Michal Siedlaczek + +# 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 . + +"""Installing and configuring spell-checking for QtWebEngine.""" + +import os +from urllib.request import urlretrieve + +from PyQt5.QtCore import QLibraryInfo + + +class Language: + + """Dictionary language specs.""" + + def __init__(self, code, name, file): + self.code = code + self.name = name + self.file = file + + @staticmethod + def from_array(lang_array): + """Create Language object from an array. + + Args: + lang_array: an array of strings containing + the specs of the language in the following format: + [code, name, file] + """ + return Language.from_tuple(tuple(lang_array)) + + @staticmethod + def from_tuple(lang_tuple): + """Create Language object from a tuple. + + Args: + lang_tuple: a tuple of strings containing + the specs of the language in the following format: + (code, name, file) + """ + code, name, file = lang_tuple + return Language(code, name, file) + + @staticmethod + def from_tsv_string(tsv_string): + """Create Language object from a string in tab-separated values format. + + Args: + tsv_string: a string containing + the specs of the language in the following format: + "code name file" + """ + lang_array = tsv_string.split('\t') + return Language.from_array(lang_array) + + def __repr__(self): + return 'Language({}, {}, {})'.format(self.code, self.name, self.file) + + +def get_dictionary_dir(): + """Return the path to the QtWebEngine's dictionaries directory.""" + return QLibraryInfo.location(QLibraryInfo.DataPath) + \ + '/qtwebengine_dictionaries' + + +def get_language_list_file(): + """Return the path to the file with the list of all available languages.""" + root_dir = os.path.dirname(os.path.abspath(__file__)) + # TODO: not sure how to determine the following path 'the right way' + return os.path.join(root_dir, '../../../', 'misc', 'lang_list') + + +def get_available_languages(): + """Return a list of Language objects of all available languages.""" + with open(get_language_list_file(), 'r', encoding='UTF-8') as file: + return [Language.from_tsv_string(line[:-1]) for line in file] + + +def get_installed_languages(): + """Return a list of Language objects of all installed languages.""" + installed_files = [os.path.basename(file) + for file in os.listdir(get_dictionary_dir())] + all_languages = get_available_languages() + return filter_languages(all_languages, installed_files, + by=lambda lang: lang.file, + fail_on_unknown=False) + + +def filter_languages(languages, selected, by=lambda lang: lang.code, + fail_on_unknown=True): + """Filter a list of languages based on an inclusion list. + + Args: + languages: a list of languages to filter + selected: a list of keys to select + by: a function returning the selection key (code by default) + fail_on_unknown: whether to raise an error if there is an unknown + key in selected + """ + filtered_languages = [] + for language in languages: + if by(language) in selected: + filtered_languages.append(language) + selected.remove(by(language)) + if fail_on_unknown and selected: + unknown = ', '.join(selected) + raise ValueError('unknown languages found: {}'.format(unknown)) + return filtered_languages + + +def install(languages): + """Install languages.""" + repository_url = 'https://redirector.gvt1.com/edgedl/chrome/dict' + for lang in languages: + try: + print('Installing {}: {}'.format(lang.code, lang.name)) + lang_url = '{}/{}'.format(repository_url, lang.file) + if not os.path.isdir(get_dictionary_dir()): + print('WARN: {} does not exist, creating the directory'.format( + get_dictionary_dir())) + os.makedirs(get_dictionary_dir()) + print('Downloading {}'.format(lang_url)) + urlretrieve(lang_url, get_dictionary_dir() + '/' + lang.file) + print('Done.') + except PermissionError as e: + print(e) diff --git a/qutebrowser/browser/webengine/webenginesettings.py b/qutebrowser/browser/webengine/webenginesettings.py index 7b4ece0e8..8130ecbde 100644 --- a/qutebrowser/browser/webengine/webenginesettings.py +++ b/qutebrowser/browser/webengine/webenginesettings.py @@ -36,6 +36,7 @@ from PyQt5.QtWebEngineWidgets import (QWebEngineSettings, QWebEngineProfile, QWebEngineScript) from qutebrowser.browser import shared +from qutebrowser.browser.webengine.spell import get_installed_languages from qutebrowser.config import config, websettings from qutebrowser.utils import utils, standarddir, javascript, qtutils @@ -127,6 +128,22 @@ class PersistentCookiePolicy(DefaultProfileSetter): ) +class DictionaryLanguageSetter(DefaultProfileSetter): + + """Sets paths to dictionary files based on language codes.""" + + def __init__(self): + super().__init__('setSpellCheckLanguages') + + def _set(self, value, settings=None): + if settings is not None: + raise ValueError("'settings' may not be set with " + "DictionaryLanguageSetter!") + files = [lang.file[:-5] + for lang in get_installed_languages() if lang.code in value] + super()._set(files, settings) + + def _init_stylesheet(profile): """Initialize custom stylesheets. @@ -299,6 +316,9 @@ MAPPINGS = { 'scrolling.smooth': Attribute(QWebEngineSettings.ScrollAnimatorEnabled), + + 'spell': DefaultProfileSetter('setSpellCheckEnabled'), + 'spell-languages': DictionaryLanguageSetter() } try: diff --git a/scripts/install_dict.py b/scripts/install_dict.py new file mode 100755 index 000000000..8d45f2f6b --- /dev/null +++ b/scripts/install_dict.py @@ -0,0 +1,66 @@ +#!/usr/bin/env python3 +# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et + +# Copyright 2017 Michal Siedlaczek + +# 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 . + +"""A script installing Hunspell dictionaries. + +Use: python -m scripts.install_dict [--list] [lang [lang [...]]] +""" + +import argparse +import sys + +from qutebrowser.browser.webengine import spell + + +def get_argparser(): + """Get the argparse parser.""" + desc = 'Install Hunspell dictionaries for QtWebEngine.' + parser = argparse.ArgumentParser(prog='install_dict', + description=desc) + parser.add_argument('-l', '--list', action='store_true', + help="Display the list of available languages.") + parser.add_argument('languages', nargs='*', + help="A list of languages to install.") + return parser + + +def print_list(languages): + for lang in languages: + print('{1}\t{0}'.format(lang.name, lang.code)) + + +def main(): + parser = get_argparser() + argv = sys.argv[1:] + args = parser.parse_args(argv) + languages = spell.get_available_languages() + if args.list: + print_list(languages) + elif not args.languages: + parser.print_usage() + else: + try: + spell.install(spell.filter_languages(languages, args.languages)) + except ValueError as e: + print(e) + + +if __name__ == '__main__': + main() diff --git a/tests/unit/browser/webengine/test_spell.py b/tests/unit/browser/webengine/test_spell.py new file mode 100644 index 000000000..d042a5991 --- /dev/null +++ b/tests/unit/browser/webengine/test_spell.py @@ -0,0 +1,69 @@ +# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: + +# Copyright 2017 Michal Siedlaczek + +# 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 . + + +from os.path import basename + +import pytest + +from qutebrowser.browser.webengine import spell + +AFRIKAANS = spell.Language('af-ZA', + 'Afrikaans (South Africa)', + 'af-ZA-3-0.bdic') +ENGLISH = spell.Language('en-US', + 'English (United States)', + 'en-US-7-1.bdic') +POLISH = spell.Language('pl-PL', + 'Polish (Poland)', + 'pl-PL-3-0.bdic') + +LANGUAGE_LIST = [AFRIKAANS, ENGLISH, POLISH] + + +def test_get_available_languages(): + language_list = spell.get_available_languages() + assert len(language_list) == 42 + first_lang = language_list[0] + assert (first_lang.code, first_lang.name, first_lang.file) ==\ + (AFRIKAANS.code, AFRIKAANS.name, AFRIKAANS.file) + + +def test_filter_languages(): + filtered_languages = spell.filter_languages(LANGUAGE_LIST, ['af-ZA']) + assert filtered_languages == [AFRIKAANS] + filtered_languages = spell.filter_languages(LANGUAGE_LIST, + ['pl-PL', 'en-US']) + assert filtered_languages == [ENGLISH, POLISH] + with pytest.raises(ValueError): + spell.filter_languages(LANGUAGE_LIST, ['pl-PL', 'en-GB']) + filtered_languages = spell.filter_languages(LANGUAGE_LIST, + ['pl-PL-3-0.bdic'], + by=lambda lang: lang.file) + assert filtered_languages == [POLISH] + + +def test_install(tmpdir, mocker): + mocker.patch('qutebrowser.browser.webengine.spell.get_dictionary_dir', + lambda: str(tmpdir)) + all_languages = spell.get_available_languages() + spell.install(all_languages) + installed_files = [basename(file) for file in tmpdir.listdir()] + expected_files = [lang.file for lang in all_languages] + assert sorted(installed_files) == sorted(expected_files) From d923ab9ae523bfc2bec81915d0aea6df62b0df3d Mon Sep 17 00:00:00 2001 From: Michal Siedlaczek Date: Sat, 12 Aug 2017 10:19:59 -0700 Subject: [PATCH 02/20] Moved and renamed the language list file --- misc/lang_list => qutebrowser/browser/webengine/langs.tsv | 0 qutebrowser/browser/webengine/spell.py | 6 +++--- 2 files changed, 3 insertions(+), 3 deletions(-) rename misc/lang_list => qutebrowser/browser/webengine/langs.tsv (100%) diff --git a/misc/lang_list b/qutebrowser/browser/webengine/langs.tsv similarity index 100% rename from misc/lang_list rename to qutebrowser/browser/webengine/langs.tsv diff --git a/qutebrowser/browser/webengine/spell.py b/qutebrowser/browser/webengine/spell.py index 5a5df344b..3e0d45702 100644 --- a/qutebrowser/browser/webengine/spell.py +++ b/qutebrowser/browser/webengine/spell.py @@ -21,6 +21,7 @@ import os from urllib.request import urlretrieve +from qutebrowser import basedir from PyQt5.QtCore import QLibraryInfo @@ -81,9 +82,8 @@ def get_dictionary_dir(): def get_language_list_file(): """Return the path to the file with the list of all available languages.""" - root_dir = os.path.dirname(os.path.abspath(__file__)) - # TODO: not sure how to determine the following path 'the right way' - return os.path.join(root_dir, '../../../', 'misc', 'lang_list') + package_dir = os.path.dirname(os.path.abspath(__file__)) + return os.path.join(package_dir, 'langs.tsv') def get_available_languages(): From 82433e04ad6d502e03d0bb5378fd968407178d6d Mon Sep 17 00:00:00 2001 From: Michal Siedlaczek Date: Sat, 12 Aug 2017 12:19:06 -0700 Subject: [PATCH 03/20] Set default value for spelling settings and limit number of installed languages for testing --- qutebrowser/browser/webengine/spell.py | 2 +- tests/unit/browser/webengine/test_spell.py | 13 ++++++++++--- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/qutebrowser/browser/webengine/spell.py b/qutebrowser/browser/webengine/spell.py index 3e0d45702..49ebbad2d 100644 --- a/qutebrowser/browser/webengine/spell.py +++ b/qutebrowser/browser/webengine/spell.py @@ -25,6 +25,7 @@ from qutebrowser import basedir from PyQt5.QtCore import QLibraryInfo +repository_url = 'https://redirector.gvt1.com/edgedl/chrome/dict' class Language: @@ -126,7 +127,6 @@ def filter_languages(languages, selected, by=lambda lang: lang.code, def install(languages): """Install languages.""" - repository_url = 'https://redirector.gvt1.com/edgedl/chrome/dict' for lang in languages: try: print('Installing {}: {}'.format(lang.code, lang.name)) diff --git a/tests/unit/browser/webengine/test_spell.py b/tests/unit/browser/webengine/test_spell.py index d042a5991..da8a0a3d6 100644 --- a/tests/unit/browser/webengine/test_spell.py +++ b/tests/unit/browser/webengine/test_spell.py @@ -19,6 +19,7 @@ from os.path import basename +#from requests import head import pytest @@ -62,8 +63,14 @@ def test_filter_languages(): def test_install(tmpdir, mocker): mocker.patch('qutebrowser.browser.webengine.spell.get_dictionary_dir', lambda: str(tmpdir)) - all_languages = spell.get_available_languages() - spell.install(all_languages) + spell.install(LANGUAGE_LIST) installed_files = [basename(file) for file in tmpdir.listdir()] - expected_files = [lang.file for lang in all_languages] + expected_files = [lang.file for lang in LANGUAGE_LIST] assert sorted(installed_files) == sorted(expected_files) + + +#def test_available_langs(): +# for lang in spell.get_available_languages(): +# lang_url = '{}/{}'.format(spell.repository_url, lang.file) +# code, text, headers = head(lang_url) +# assert code == 200 From fac0e44a7e3410e3a6bf312d295a0a652a778cec Mon Sep 17 00:00:00 2001 From: Michal Siedlaczek Date: Sat, 12 Aug 2017 16:09:52 -0700 Subject: [PATCH 04/20] Test all available languages and getting installed langs when the dir doesn't exist --- qutebrowser/browser/webengine/spell.py | 2 ++ tests/unit/browser/webengine/test_spell.py | 21 ++++++++++++++------- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/qutebrowser/browser/webengine/spell.py b/qutebrowser/browser/webengine/spell.py index 49ebbad2d..b61ca033c 100644 --- a/qutebrowser/browser/webengine/spell.py +++ b/qutebrowser/browser/webengine/spell.py @@ -95,6 +95,8 @@ def get_available_languages(): def get_installed_languages(): """Return a list of Language objects of all installed languages.""" + if not os.path.isdir(get_dictionary_dir()): + return [] installed_files = [os.path.basename(file) for file in os.listdir(get_dictionary_dir())] all_languages = get_available_languages() diff --git a/tests/unit/browser/webengine/test_spell.py b/tests/unit/browser/webengine/test_spell.py index da8a0a3d6..65e5acba9 100644 --- a/tests/unit/browser/webengine/test_spell.py +++ b/tests/unit/browser/webengine/test_spell.py @@ -19,7 +19,7 @@ from os.path import basename -#from requests import head +from requests import head import pytest @@ -38,6 +38,13 @@ POLISH = spell.Language('pl-PL', LANGUAGE_LIST = [AFRIKAANS, ENGLISH, POLISH] +def test_get_installed_languages(mocker): + # return an empty list if the dir doesn't exist + mocker.patch('qutebrowser.browser.webengine.spell.get_dictionary_dir', + lambda: '/some-non-existing-dir') + assert spell.get_installed_languages() == [] + + def test_get_available_languages(): language_list = spell.get_available_languages() assert len(language_list) == 42 @@ -64,13 +71,13 @@ def test_install(tmpdir, mocker): mocker.patch('qutebrowser.browser.webengine.spell.get_dictionary_dir', lambda: str(tmpdir)) spell.install(LANGUAGE_LIST) - installed_files = [basename(file) for file in tmpdir.listdir()] + installed_files = [basename(str(file)) for file in tmpdir.listdir()] expected_files = [lang.file for lang in LANGUAGE_LIST] assert sorted(installed_files) == sorted(expected_files) -#def test_available_langs(): -# for lang in spell.get_available_languages(): -# lang_url = '{}/{}'.format(spell.repository_url, lang.file) -# code, text, headers = head(lang_url) -# assert code == 200 +def test_available_langs(): + for lang in spell.get_available_languages(): + lang_url = '{}/{}'.format(spell.repository_url, lang.file) + response = head(lang_url) + assert response.status_code == 302 From 95592770a796331b26e61f4f94a3d2facfe6c3d4 Mon Sep 17 00:00:00 2001 From: Michal Siedlaczek Date: Sat, 12 Aug 2017 17:29:57 -0700 Subject: [PATCH 05/20] Fixing test dependencies and other test issues --- misc/requirements/requirements-tests.txt-raw | 1 + qutebrowser/browser/webengine/spell.py | 16 +++++++++------- scripts/install_dict.py | 2 +- tests/unit/browser/webengine/test_spell.py | 5 +++-- 4 files changed, 14 insertions(+), 10 deletions(-) diff --git a/misc/requirements/requirements-tests.txt-raw b/misc/requirements/requirements-tests.txt-raw index bc44bc8e1..8693ed31f 100644 --- a/misc/requirements/requirements-tests.txt-raw +++ b/misc/requirements/requirements-tests.txt-raw @@ -17,6 +17,7 @@ pytest-repeat pytest-rerunfailures pytest-travis-fold pytest-xvfb +requests vulture #@ ignore: Jinja2, MarkupSafe, colorama diff --git a/qutebrowser/browser/webengine/spell.py b/qutebrowser/browser/webengine/spell.py index b61ca033c..66c87f2a5 100644 --- a/qutebrowser/browser/webengine/spell.py +++ b/qutebrowser/browser/webengine/spell.py @@ -1,4 +1,4 @@ -# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et +# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: # Copyright 2017 Michal Siedlaczek @@ -20,12 +20,13 @@ """Installing and configuring spell-checking for QtWebEngine.""" import os +from urllib.parse import urljoin from urllib.request import urlretrieve -from qutebrowser import basedir from PyQt5.QtCore import QLibraryInfo -repository_url = 'https://redirector.gvt1.com/edgedl/chrome/dict' +repository_url = 'https://redirector.gvt1.com/edgedl/chrome/dict/' + class Language: @@ -77,8 +78,8 @@ class Language: def get_dictionary_dir(): """Return the path to the QtWebEngine's dictionaries directory.""" - return QLibraryInfo.location(QLibraryInfo.DataPath) + \ - '/qtwebengine_dictionaries' + return os.path.join(QLibraryInfo.location(QLibraryInfo.DataPath), + '/qtwebengine_dictionaries') def get_language_list_file(): @@ -132,13 +133,14 @@ def install(languages): for lang in languages: try: print('Installing {}: {}'.format(lang.code, lang.name)) - lang_url = '{}/{}'.format(repository_url, lang.file) + lang_url = urljoin(repository_url, lang.file) if not os.path.isdir(get_dictionary_dir()): print('WARN: {} does not exist, creating the directory'.format( get_dictionary_dir())) os.makedirs(get_dictionary_dir()) print('Downloading {}'.format(lang_url)) - urlretrieve(lang_url, get_dictionary_dir() + '/' + lang.file) + urlretrieve(lang_url, + os.path.join(get_dictionary_dir(), lang.file)) print('Done.') except PermissionError as e: print(e) diff --git a/scripts/install_dict.py b/scripts/install_dict.py index 8d45f2f6b..7cedf8c23 100755 --- a/scripts/install_dict.py +++ b/scripts/install_dict.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et +# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: # Copyright 2017 Michal Siedlaczek diff --git a/tests/unit/browser/webengine/test_spell.py b/tests/unit/browser/webengine/test_spell.py index 65e5acba9..a5c1c3db5 100644 --- a/tests/unit/browser/webengine/test_spell.py +++ b/tests/unit/browser/webengine/test_spell.py @@ -19,9 +19,10 @@ from os.path import basename -from requests import head +from urllib.parse import urljoin import pytest +from requests import head from qutebrowser.browser.webengine import spell @@ -78,6 +79,6 @@ def test_install(tmpdir, mocker): def test_available_langs(): for lang in spell.get_available_languages(): - lang_url = '{}/{}'.format(spell.repository_url, lang.file) + lang_url = urljoin(spell.repository_url, lang.file) response = head(lang_url) assert response.status_code == 302 From e61e6b124eb71fe5462bde83a90925d35c418996 Mon Sep 17 00:00:00 2001 From: Michal Siedlaczek Date: Sat, 19 Aug 2017 11:15:15 -0700 Subject: [PATCH 06/20] Check if spelling supported by QWebEngineProfile (version 5.8 or higher) --- misc/requirements/requirements-pylint.txt | 1 - qutebrowser/browser/webengine/webenginesettings.py | 3 ++- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/misc/requirements/requirements-pylint.txt b/misc/requirements/requirements-pylint.txt index 777d47fdd..7463184a4 100644 --- a/misc/requirements/requirements-pylint.txt +++ b/misc/requirements/requirements-pylint.txt @@ -4,7 +4,6 @@ astroid==1.5.3 certifi==2017.7.27.1 chardet==3.0.4 github3.py==0.9.6 -idna==2.6 isort==4.2.15 lazy-object-proxy==1.3.1 mccabe==0.6.1 diff --git a/qutebrowser/browser/webengine/webenginesettings.py b/qutebrowser/browser/webengine/webenginesettings.py index 8130ecbde..1ee017660 100644 --- a/qutebrowser/browser/webengine/webenginesettings.py +++ b/qutebrowser/browser/webengine/webenginesettings.py @@ -133,7 +133,8 @@ class DictionaryLanguageSetter(DefaultProfileSetter): """Sets paths to dictionary files based on language codes.""" def __init__(self): - super().__init__('setSpellCheckLanguages') + super().__init__('setSpellCheckLanguages', default=[], + min_version='5.8') def _set(self, value, settings=None): if settings is not None: From cf229cb9c8e91cc40df4a216e557dfa3bda760f7 Mon Sep 17 00:00:00 2001 From: Michal Siedlaczek Date: Sun, 20 Aug 2017 10:39:09 -0700 Subject: [PATCH 07/20] Dictionary path bug fix & added warning whenever a selected dictionary isn't installed --- qutebrowser/browser/webengine/spell.py | 2 +- qutebrowser/browser/webengine/webenginesettings.py | 13 ++++++++++--- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/qutebrowser/browser/webengine/spell.py b/qutebrowser/browser/webengine/spell.py index 66c87f2a5..99595160c 100644 --- a/qutebrowser/browser/webengine/spell.py +++ b/qutebrowser/browser/webengine/spell.py @@ -79,7 +79,7 @@ class Language: def get_dictionary_dir(): """Return the path to the QtWebEngine's dictionaries directory.""" return os.path.join(QLibraryInfo.location(QLibraryInfo.DataPath), - '/qtwebengine_dictionaries') + 'qtwebengine_dictionaries') def get_language_list_file(): diff --git a/qutebrowser/browser/webengine/webenginesettings.py b/qutebrowser/browser/webengine/webenginesettings.py index 1ee017660..9124fec06 100644 --- a/qutebrowser/browser/webengine/webenginesettings.py +++ b/qutebrowser/browser/webengine/webenginesettings.py @@ -140,9 +140,16 @@ class DictionaryLanguageSetter(DefaultProfileSetter): if settings is not None: raise ValueError("'settings' may not be set with " "DictionaryLanguageSetter!") - files = [lang.file[:-5] - for lang in get_installed_languages() if lang.code in value] - super()._set(files, settings) + installed_langs = dict([(lang.code, lang.file) + for lang in get_installed_languages()]) + lang_files = [] + for lang_code in value: + if lang_code in installed_langs: + lang_files.append(installed_langs[lang_code][:-5]) + else: + message.warning('Language {} is not installed.' + .format(lang_code)) + super()._set(lang_files, settings) def _init_stylesheet(profile): From 329cfa57562d3a61adaa3a165cae241c81bc8005 Mon Sep 17 00:00:00 2001 From: Michal Siedlaczek Date: Sun, 20 Aug 2017 14:36:35 -0700 Subject: [PATCH 08/20] End2end tests for spell checking --- qutebrowser/misc/utilcmds.py | 1 + tests/end2end/features/spell.feature | 38 ++++++++++++++++ tests/end2end/features/test_spell_bdd.py | 58 ++++++++++++++++++++++++ 3 files changed, 97 insertions(+) create mode 100644 tests/end2end/features/spell.feature create mode 100644 tests/end2end/features/test_spell_bdd.py diff --git a/qutebrowser/misc/utilcmds.py b/qutebrowser/misc/utilcmds.py index 4b6909344..d8cfe2366 100644 --- a/qutebrowser/misc/utilcmds.py +++ b/qutebrowser/misc/utilcmds.py @@ -33,6 +33,7 @@ import sip from PyQt5.QtCore import QUrl # so it's available for :debug-pyeval from PyQt5.QtWidgets import QApplication # pylint: disable=unused-import +from PyQt5.QtWebEngineWidgets import QWebEngineProfile # pylint: disable=unused-import from qutebrowser.browser import qutescheme from qutebrowser.utils import log, objreg, usertypes, message, debug, utils diff --git a/tests/end2end/features/spell.feature b/tests/end2end/features/spell.feature new file mode 100644 index 000000000..8195c49e1 --- /dev/null +++ b/tests/end2end/features/spell.feature @@ -0,0 +1,38 @@ +# vim: ft=cucumber fileencoding=utf-8 sts=4 sw=4 et: + +Feature: Setting spell checking for QtWebEngine + + Background: + Given spell check languages are [] + + @qtwebkit_skip @qt>=5.8 + Scenario: Turn spell check on + Given spell check is off + When I run :set ui spell true + Then ui -> spell should be true + Then spell check is on + + @qtwebkit_skip @qt>=5.8 + Scenario: Turn spell check off + Given spell check is on + When I run :set ui spell false + Then ui -> spell should be false + Then spell check is off + + @qtwebkit_skip @qt>=5.8 + Scenario: Set an invalid language + When I run :set ui spell-languages invalid-language (invalid command) + Then the error "set: Invalid value 'invalid-language' *" should be shown + Then actual spell check languages are [] + + @qtwebkit_skip @qt>=5.8 + Scenario: Set valid but not installed language + When I run :set ui spell-languages af-ZA + Then the warning "Language af-ZA is not installed." should be shown + Then actual spell check languages are [] + + @qtwebkit_skip @qt>=5.8 + Scenario: Set valid and installed language + When I run :set ui spell-languages en-US + Then ui -> spell-languages should be en-US + Then actual spell check languages are ['en-US-7-1'] diff --git a/tests/end2end/features/test_spell_bdd.py b/tests/end2end/features/test_spell_bdd.py new file mode 100644 index 000000000..f6025e66b --- /dev/null +++ b/tests/end2end/features/test_spell_bdd.py @@ -0,0 +1,58 @@ +# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: + +# Copyright 2017 Michał Siedlaczek +# +# 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 . + +import pytest_bdd as bdd + +bdd.scenarios('spell.feature') + + +@bdd.given(bdd.parsers.parse("spell check is {val}")) +def spellcheck_enabled_given(quteproc, val): + enabled = val == 'on' + quteproc.send_cmd(':debug-pyeval QWebEngineProfile.defaultProfile()' + + '.setSpellCheckEnabled({})'.format(enabled)) + quteproc.wait_for_load_finished('qute://pyeval') + + +@bdd.given(bdd.parsers.parse("spell check languages are {langs}")) +def spellcheck_langs_given(quteproc, langs): + quteproc.send_cmd(':debug-pyeval QWebEngineProfile.defaultProfile()' + + '.setSpellCheckLanguages({})'.format(langs)) + quteproc.wait_for_load_finished('qute://pyeval') + + +@bdd.then(bdd.parsers.parse("spell check is {val}")) +def spellcheck_enabled_then(quteproc, val): + quteproc.send_cmd(':debug-pyeval QWebEngineProfile.defaultProfile()' + + '.isSpellCheckEnabled()') + quteproc.wait_for_load_finished('qute://pyeval') + content = quteproc.get_content().strip() + if val == 'on': + assert content == 'True' + else: + assert content == 'False' + + +@bdd.then(bdd.parsers.parse("actual spell check languages are {langs}")) +def spellcheck_langs_then(quteproc, langs): + quteproc.send_cmd(':debug-pyeval QWebEngineProfile.defaultProfile()' + + '.spellCheckLanguages()') + quteproc.wait_for_load_finished('qute://pyeval') + actual_langs = quteproc.get_content().strip() + assert actual_langs == langs From 132b1f705ee313442bc1ec6f1ddcd22c702773e2 Mon Sep 17 00:00:00 2001 From: Michal Siedlaczek Date: Thu, 21 Sep 2017 09:42:21 -0400 Subject: [PATCH 09/20] Revert requirements changes --- misc/requirements/requirements-codecov.txt | 8 +++--- misc/requirements/requirements-pylint.txt | 1 + misc/requirements/requirements-tests.txt-raw | 1 - qutebrowser/browser/webengine/spell.py | 8 ++++-- tests/unit/browser/webengine/test_spell.py | 30 +++++++++++++------- 5 files changed, 31 insertions(+), 17 deletions(-) diff --git a/misc/requirements/requirements-codecov.txt b/misc/requirements/requirements-codecov.txt index c971e2306..86f78562d 100644 --- a/misc/requirements/requirements-codecov.txt +++ b/misc/requirements/requirements-codecov.txt @@ -1,9 +1,9 @@ # This file is automatically generated by scripts/dev/recompile_requirements.py -certifi==2017.7.27.1 +certifi==2017.4.17 chardet==3.0.4 codecov==2.0.9 coverage==4.4.1 -idna==2.6 -requests==2.18.4 -urllib3==1.22 +idna==2.5 +requests==2.18.1 +urllib3==1.21.1 diff --git a/misc/requirements/requirements-pylint.txt b/misc/requirements/requirements-pylint.txt index 7463184a4..777d47fdd 100644 --- a/misc/requirements/requirements-pylint.txt +++ b/misc/requirements/requirements-pylint.txt @@ -4,6 +4,7 @@ astroid==1.5.3 certifi==2017.7.27.1 chardet==3.0.4 github3.py==0.9.6 +idna==2.6 isort==4.2.15 lazy-object-proxy==1.3.1 mccabe==0.6.1 diff --git a/misc/requirements/requirements-tests.txt-raw b/misc/requirements/requirements-tests.txt-raw index 8693ed31f..bc44bc8e1 100644 --- a/misc/requirements/requirements-tests.txt-raw +++ b/misc/requirements/requirements-tests.txt-raw @@ -17,7 +17,6 @@ pytest-repeat pytest-rerunfailures pytest-travis-fold pytest-xvfb -requests vulture #@ ignore: Jinja2, MarkupSafe, colorama diff --git a/qutebrowser/browser/webengine/spell.py b/qutebrowser/browser/webengine/spell.py index 99595160c..5f40d189e 100644 --- a/qutebrowser/browser/webengine/spell.py +++ b/qutebrowser/browser/webengine/spell.py @@ -128,6 +128,10 @@ def filter_languages(languages, selected, by=lambda lang: lang.code, return filtered_languages +def download_dictionary(url, dest): + urlretrieve(url, dest) + + def install(languages): """Install languages.""" for lang in languages: @@ -139,8 +143,8 @@ def install(languages): get_dictionary_dir())) os.makedirs(get_dictionary_dir()) print('Downloading {}'.format(lang_url)) - urlretrieve(lang_url, - os.path.join(get_dictionary_dir(), lang.file)) + download_dictionary(lang_url, os.path.join(get_dictionary_dir(), + lang.file)) print('Done.') except PermissionError as e: print(e) diff --git a/tests/unit/browser/webengine/test_spell.py b/tests/unit/browser/webengine/test_spell.py index a5c1c3db5..e7d353cbf 100644 --- a/tests/unit/browser/webengine/test_spell.py +++ b/tests/unit/browser/webengine/test_spell.py @@ -18,11 +18,9 @@ # along with qutebrowser. If not, see . -from os.path import basename -from urllib.parse import urljoin +from os.path import basename, join import pytest -from requests import head from qutebrowser.browser.webengine import spell @@ -39,13 +37,22 @@ POLISH = spell.Language('pl-PL', LANGUAGE_LIST = [AFRIKAANS, ENGLISH, POLISH] -def test_get_installed_languages(mocker): - # return an empty list if the dir doesn't exist +def test_get_installed_languages_empty(tmpdir, mocker): mocker.patch('qutebrowser.browser.webengine.spell.get_dictionary_dir', lambda: '/some-non-existing-dir') assert spell.get_installed_languages() == [] +def test_get_installed_languages_non_empty(tmpdir, mocker): + mocker.patch('qutebrowser.browser.webengine.spell.get_dictionary_dir', + lambda: str(tmpdir)) + for lang in LANGUAGE_LIST: + open(join(tmpdir, lang.file), 'w').close() + for actual, expected in zip(spell.get_installed_languages(), LANGUAGE_LIST): + assert (actual.code, actual.name, actual.file) ==\ + (expected.code, expected.name, expected.file) + + def test_get_available_languages(): language_list = spell.get_available_languages() assert len(language_list) == 42 @@ -71,14 +78,17 @@ def test_filter_languages(): def test_install(tmpdir, mocker): mocker.patch('qutebrowser.browser.webengine.spell.get_dictionary_dir', lambda: str(tmpdir)) + mocker.patch('qutebrowser.browser.webengine.spell.download_dictionary', + lambda url, dest: open(dest, 'w').close()) spell.install(LANGUAGE_LIST) installed_files = [basename(str(file)) for file in tmpdir.listdir()] expected_files = [lang.file for lang in LANGUAGE_LIST] assert sorted(installed_files) == sorted(expected_files) -def test_available_langs(): - for lang in spell.get_available_languages(): - lang_url = urljoin(spell.repository_url, lang.file) - response = head(lang_url) - assert response.status_code == 302 +# TODO: move to update_3rdparty.py +#def test_available_langs(): +# for lang in spell.get_available_languages(): +# lang_url = urljoin(spell.repository_url, lang.file) +# response = head(lang_url) +# assert response.status_code == 302 From 9e620ce6e9c71edd2b8b02eea4e85698cec0b836 Mon Sep 17 00:00:00 2001 From: Michal Siedlaczek Date: Thu, 21 Sep 2017 12:09:55 -0400 Subject: [PATCH 10/20] Fix spell with new config --- MANIFEST.in | 2 +- .../browser/webengine/webenginesettings.py | 10 ++++------ qutebrowser/config/configdata.yml | 15 +++++++++++++++ tests/unit/browser/webengine/test_spell.py | 7 ++++--- 4 files changed, 24 insertions(+), 10 deletions(-) diff --git a/MANIFEST.in b/MANIFEST.in index d6f9e713a..b8ae5c718 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -18,7 +18,7 @@ include tox.ini include qutebrowser.py include misc/cheatsheet.svg include qutebrowser/config/configdata.yml -include misc/lang_list +include qutebrowser/browser/webengine/langs.tsv prune www prune scripts/dev diff --git a/qutebrowser/browser/webengine/webenginesettings.py b/qutebrowser/browser/webengine/webenginesettings.py index 9124fec06..ed87dd88f 100644 --- a/qutebrowser/browser/webengine/webenginesettings.py +++ b/qutebrowser/browser/webengine/webenginesettings.py @@ -27,9 +27,9 @@ Module attributes: constants. """ -import os import ctypes import ctypes.util +import os from PyQt5.QtGui import QFont from PyQt5.QtWebEngineWidgets import (QWebEngineSettings, QWebEngineProfile, @@ -38,8 +38,7 @@ from PyQt5.QtWebEngineWidgets import (QWebEngineSettings, QWebEngineProfile, from qutebrowser.browser import shared from qutebrowser.browser.webengine.spell import get_installed_languages from qutebrowser.config import config, websettings -from qutebrowser.utils import utils, standarddir, javascript, qtutils - +from qutebrowser.utils import utils, standarddir, javascript, qtutils, message # The default QWebEngineProfile default_profile = None @@ -133,8 +132,7 @@ class DictionaryLanguageSetter(DefaultProfileSetter): """Sets paths to dictionary files based on language codes.""" def __init__(self): - super().__init__('setSpellCheckLanguages', default=[], - min_version='5.8') + super().__init__('setSpellCheckLanguages', default=[]) def _set(self, value, settings=None): if settings is not None: @@ -326,7 +324,7 @@ MAPPINGS = { Attribute(QWebEngineSettings.ScrollAnimatorEnabled), 'spell': DefaultProfileSetter('setSpellCheckEnabled'), - 'spell-languages': DictionaryLanguageSetter() + 'spell_languages': DictionaryLanguageSetter() } try: diff --git a/qutebrowser/config/configdata.yml b/qutebrowser/config/configdata.yml index f0e5ae74d..3eae6890e 100644 --- a/qutebrowser/config/configdata.yml +++ b/qutebrowser/config/configdata.yml @@ -975,6 +975,21 @@ scrolling.smooth: Note smooth scrolling does not work with the `:scroll-px` command. +## spell + +spell: + type: Bool + default: true + desc: Enable spell checking. + +spell_languages: + type: + name: List + valtype: String + none_ok: true + default: + desc: Spell checking languages. + ## statusbar statusbar.hide: diff --git a/tests/unit/browser/webengine/test_spell.py b/tests/unit/browser/webengine/test_spell.py index e7d353cbf..8c802881c 100644 --- a/tests/unit/browser/webengine/test_spell.py +++ b/tests/unit/browser/webengine/test_spell.py @@ -47,8 +47,9 @@ def test_get_installed_languages_non_empty(tmpdir, mocker): mocker.patch('qutebrowser.browser.webengine.spell.get_dictionary_dir', lambda: str(tmpdir)) for lang in LANGUAGE_LIST: - open(join(tmpdir, lang.file), 'w').close() - for actual, expected in zip(spell.get_installed_languages(), LANGUAGE_LIST): + open(join(tmpdir, lang.file), 'w', encoding='UTF-8').close() + for actual, expected in zip(spell.get_installed_languages(), + LANGUAGE_LIST): assert (actual.code, actual.name, actual.file) ==\ (expected.code, expected.name, expected.file) @@ -79,7 +80,7 @@ def test_install(tmpdir, mocker): mocker.patch('qutebrowser.browser.webengine.spell.get_dictionary_dir', lambda: str(tmpdir)) mocker.patch('qutebrowser.browser.webengine.spell.download_dictionary', - lambda url, dest: open(dest, 'w').close()) + lambda url, dest: open(dest, 'w', encoding='UTF-8').close()) spell.install(LANGUAGE_LIST) installed_files = [basename(str(file)) for file in tmpdir.listdir()] expected_files = [lang.file for lang in LANGUAGE_LIST] From 396f82d47482885cd87e97263591530defec99c1 Mon Sep 17 00:00:00 2001 From: Michal Siedlaczek Date: Thu, 21 Sep 2017 16:30:58 -0400 Subject: [PATCH 11/20] Valid dictionaries --- qutebrowser/config/configdata.yml | 46 ++++++++++++++++++++++++++++++- 1 file changed, 45 insertions(+), 1 deletion(-) diff --git a/qutebrowser/config/configdata.yml b/qutebrowser/config/configdata.yml index 3eae6890e..3bb9b65af 100644 --- a/qutebrowser/config/configdata.yml +++ b/qutebrowser/config/configdata.yml @@ -985,7 +985,51 @@ spell: spell_languages: type: name: List - valtype: String + valtype: + name: String + valid_values: + - af-ZA: Afrikaans (South Africa) + - bg-BG: Bulgarian (Bulgaria) + - ca-ES: Catalan (Spain) + - cs-CZ: Czech (Czech Republic) + - da-DK: Danish (Denmark) + - de-DE: German (Germany) + - el-GR: Greek (Greece) + - en-CA: English (Canada) + - en-GB: English (United Kingdom) + - en-US: English (United States) + - es-ES: Spanish (Spain) + - et-EE: Estonian (Estonia) + - fa-IR: Farsi (Iran) + - fo-FO: Faroese (Faroe Islands) + - fr-FR: French (France) + - he-IL: Hebrew (Israel) + - hi-IN: Hindi (India) + - hr-HR: Croatian (Croatia) + - hu-HU: Hungarian (Hungary) + - id-ID: Indonesian (Indonesia) + - it-IT: Italian (Italy) + - ko: Korean + - lt-LT: Lithuanian (Lithuania) + - lv-LV: Latvian (Latvia) + - nb-NO: Norwegian (Norway) + - nl-NL: Dutch (Netherlands) + - pl-PL: Polish (Poland) + - pt-BR: Portuguese (Brazil) + - pt-PT: Portuguese (Portugal) + - ro-RO: Romanian (Romania) + - ru-RU: Russian (Russia) + - sh: Serbo-Croatian + - sk-SK: Slovak (Slovakia) + - sl-SI: Slovenian (Slovenia) + - sq: Albanian + - sr: Serbian + - sv-SE: Swedish (Sweden) + - ta-IN: Tamil (India) + - tg-TG: Tajik (Tajikistan) + - tr-TR: Turkish (Turkey) + - uk-UA: Ukrainian (Ukraine) + - vi-VN: Vietnamese (Viet Nam) none_ok: true default: desc: Spell checking languages. From 2150154350bf8a75cc5933083a5b1d01bfa2819c Mon Sep 17 00:00:00 2001 From: Michal Siedlaczek Date: Thu, 21 Sep 2017 16:31:37 -0400 Subject: [PATCH 12/20] Skip end2end spell tests if a dictionary is/isn't installed --- tests/end2end/conftest.py | 26 +++++++++++++++++++++- tests/end2end/features/spell.feature | 20 ++++++++--------- tests/unit/browser/webengine/test_spell.py | 2 +- 3 files changed, 36 insertions(+), 12 deletions(-) diff --git a/tests/end2end/conftest.py b/tests/end2end/conftest.py index 51a0497bf..3ec6b79c7 100644 --- a/tests/end2end/conftest.py +++ b/tests/end2end/conftest.py @@ -29,6 +29,8 @@ import pstats import os.path import operator +from qutebrowser.browser.webengine.spell import get_installed_languages + import pytest from PyQt5.QtCore import PYQT_VERSION @@ -116,6 +118,28 @@ def _get_backend_tag(tag): return pytest_marks[name](desc) +def _get_dictionary_tag(tag): + """Handle tags like must_have_dict=en-US for BDD tests.""" + version_re = re.compile(r""" + (?Pmust_have_dict|cannot_have_dict)=(?P[a-z]{2}-[A-Z]{2}) + """, re.VERBOSE) + + match = version_re.match(tag) + if not match: + #return pytest.mark.skip + return None + + event = match.group('event') + dict = match.group('dict') + has_dict = dict in [lang.code for lang in get_installed_languages()] + if event == 'must_have_dict': + return pytest.mark.skipif(not has_dict, reason=tag) + elif event == 'cannot_have_dict': + return pytest.mark.skipif(has_dict, reason=tag) + else: + return None + + if not getattr(sys, 'frozen', False): def pytest_bdd_apply_tag(tag, function): """Handle custom tags for BDD tests. @@ -123,7 +147,7 @@ if not getattr(sys, 'frozen', False): This tries various functions, and if none knows how to handle this tag, it returns None so it falls back to pytest-bdd's implementation. """ - funcs = [_get_version_tag, _get_backend_tag] + funcs = [_get_version_tag, _get_backend_tag, _get_dictionary_tag] for func in funcs: mark = func(tag) if mark is not None: diff --git a/tests/end2end/features/spell.feature b/tests/end2end/features/spell.feature index 8195c49e1..8fb996ec3 100644 --- a/tests/end2end/features/spell.feature +++ b/tests/end2end/features/spell.feature @@ -8,31 +8,31 @@ Feature: Setting spell checking for QtWebEngine @qtwebkit_skip @qt>=5.8 Scenario: Turn spell check on Given spell check is off - When I run :set ui spell true - Then ui -> spell should be true + When I run :set spell true + Then the option spell should be set to true Then spell check is on @qtwebkit_skip @qt>=5.8 Scenario: Turn spell check off Given spell check is on - When I run :set ui spell false - Then ui -> spell should be false + When I run :set spell false + Then the option spell should be set to false Then spell check is off @qtwebkit_skip @qt>=5.8 Scenario: Set an invalid language - When I run :set ui spell-languages invalid-language (invalid command) + When I run :set spell_languages ['invalid-language'] (invalid command) Then the error "set: Invalid value 'invalid-language' *" should be shown Then actual spell check languages are [] - @qtwebkit_skip @qt>=5.8 + @qtwebkit_skip @qt>=5.8 @cannot_have_dict=af-ZA Scenario: Set valid but not installed language - When I run :set ui spell-languages af-ZA + When I run :set spell_languages ['af-ZA'] Then the warning "Language af-ZA is not installed." should be shown Then actual spell check languages are [] - @qtwebkit_skip @qt>=5.8 + @qtwebkit_skip @qt>=5.8 @must_have_dict=en-US Scenario: Set valid and installed language - When I run :set ui spell-languages en-US - Then ui -> spell-languages should be en-US + When I run :set spell_languages ["en-US"] + Then the option spell_languages should be set to ["en-US"] Then actual spell check languages are ['en-US-7-1'] diff --git a/tests/unit/browser/webengine/test_spell.py b/tests/unit/browser/webengine/test_spell.py index 8c802881c..faee84371 100644 --- a/tests/unit/browser/webengine/test_spell.py +++ b/tests/unit/browser/webengine/test_spell.py @@ -87,7 +87,7 @@ def test_install(tmpdir, mocker): assert sorted(installed_files) == sorted(expected_files) -# TODO: move to update_3rdparty.py +# TODO: move somewhere to be checked before a release #def test_available_langs(): # for lang in spell.get_available_languages(): # lang_url = urljoin(spell.repository_url, lang.file) From b840b8066b8b67bfe6b426caf99175f78d4bd8c9 Mon Sep 17 00:00:00 2001 From: Michal Siedlaczek Date: Fri, 22 Sep 2017 11:16:59 -0400 Subject: [PATCH 13/20] Spell only when pyqt>=5.8 --- qutebrowser/browser/webengine/webenginesettings.py | 7 +++++-- qutebrowser/config/configdata.yml | 6 ++++++ tests/end2end/conftest.py | 4 ++-- tests/unit/browser/webengine/test_spell.py | 2 +- 4 files changed, 14 insertions(+), 5 deletions(-) diff --git a/qutebrowser/browser/webengine/webenginesettings.py b/qutebrowser/browser/webengine/webenginesettings.py index ed87dd88f..1f21aa2d5 100644 --- a/qutebrowser/browser/webengine/webenginesettings.py +++ b/qutebrowser/browser/webengine/webenginesettings.py @@ -323,8 +323,6 @@ MAPPINGS = { 'scrolling.smooth': Attribute(QWebEngineSettings.ScrollAnimatorEnabled), - 'spell': DefaultProfileSetter('setSpellCheckEnabled'), - 'spell_languages': DictionaryLanguageSetter() } try: @@ -335,6 +333,11 @@ except AttributeError: pass +if qtutils.version_check('5.8'): + MAPPINGS['spell'] = DefaultProfileSetter('setSpellCheckEnabled') + MAPPINGS['spell_languages'] = DictionaryLanguageSetter() + + if qtutils.version_check('5.9'): # https://bugreports.qt.io/browse/QTBUG-58650 MAPPINGS['content.cookies.store'] = PersistentCookiePolicy() diff --git a/qutebrowser/config/configdata.yml b/qutebrowser/config/configdata.yml index 3bb9b65af..77b098e68 100644 --- a/qutebrowser/config/configdata.yml +++ b/qutebrowser/config/configdata.yml @@ -981,6 +981,9 @@ spell: type: Bool default: true desc: Enable spell checking. + backend: + QtWebKit: false + QtWebEngine: Qt 5.8 spell_languages: type: @@ -1033,6 +1036,9 @@ spell_languages: none_ok: true default: desc: Spell checking languages. + backend: + QtWebKit: false + QtWebEngine: Qt 5.8 ## statusbar diff --git a/tests/end2end/conftest.py b/tests/end2end/conftest.py index 3ec6b79c7..432905ff7 100644 --- a/tests/end2end/conftest.py +++ b/tests/end2end/conftest.py @@ -130,8 +130,8 @@ def _get_dictionary_tag(tag): return None event = match.group('event') - dict = match.group('dict') - has_dict = dict in [lang.code for lang in get_installed_languages()] + dictionary = match.group('dict') + has_dict = dictionary in [lang.code for lang in get_installed_languages()] if event == 'must_have_dict': return pytest.mark.skipif(not has_dict, reason=tag) elif event == 'cannot_have_dict': diff --git a/tests/unit/browser/webengine/test_spell.py b/tests/unit/browser/webengine/test_spell.py index faee84371..4a5496e20 100644 --- a/tests/unit/browser/webengine/test_spell.py +++ b/tests/unit/browser/webengine/test_spell.py @@ -47,7 +47,7 @@ def test_get_installed_languages_non_empty(tmpdir, mocker): mocker.patch('qutebrowser.browser.webengine.spell.get_dictionary_dir', lambda: str(tmpdir)) for lang in LANGUAGE_LIST: - open(join(tmpdir, lang.file), 'w', encoding='UTF-8').close() + open(join(str(tmpdir), lang.file), 'w', encoding='UTF-8').close() for actual, expected in zip(spell.get_installed_languages(), LANGUAGE_LIST): assert (actual.code, actual.name, actual.file) ==\ From 932e7a9ab901169bee05912372ffd0af81a9d1dc Mon Sep 17 00:00:00 2001 From: Michal Siedlaczek Date: Tue, 3 Oct 2017 19:54:54 -0400 Subject: [PATCH 14/20] Review fixes --- MANIFEST.in | 1 - misc/requirements/requirements-codecov.txt | 8 +- qutebrowser/browser/webengine/langs.tsv | 42 ------ qutebrowser/browser/webengine/spell.py | 139 +++--------------- .../browser/webengine/webenginesettings.py | 26 ++-- qutebrowser/config/configdata.yml | 10 +- qutebrowser/misc/utilcmds.py | 1 - scripts/dev/update_3rdparty.py | 30 +++- scripts/install_dict.py | 116 ++++++++++++++- tests/end2end/conftest.py | 9 +- tests/end2end/features/spell.feature | 28 ++-- tests/end2end/features/test_spell_bdd.py | 36 ----- tests/unit/browser/webengine/test_spell.py | 81 ++-------- tests/unit/scripts/test_install_dict.py | 71 +++++++++ 14 files changed, 281 insertions(+), 317 deletions(-) delete mode 100644 qutebrowser/browser/webengine/langs.tsv create mode 100644 tests/unit/scripts/test_install_dict.py diff --git a/MANIFEST.in b/MANIFEST.in index b8ae5c718..ec906aaf4 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -18,7 +18,6 @@ include tox.ini include qutebrowser.py include misc/cheatsheet.svg include qutebrowser/config/configdata.yml -include qutebrowser/browser/webengine/langs.tsv prune www prune scripts/dev diff --git a/misc/requirements/requirements-codecov.txt b/misc/requirements/requirements-codecov.txt index 86f78562d..c971e2306 100644 --- a/misc/requirements/requirements-codecov.txt +++ b/misc/requirements/requirements-codecov.txt @@ -1,9 +1,9 @@ # This file is automatically generated by scripts/dev/recompile_requirements.py -certifi==2017.4.17 +certifi==2017.7.27.1 chardet==3.0.4 codecov==2.0.9 coverage==4.4.1 -idna==2.5 -requests==2.18.1 -urllib3==1.21.1 +idna==2.6 +requests==2.18.4 +urllib3==1.22 diff --git a/qutebrowser/browser/webengine/langs.tsv b/qutebrowser/browser/webengine/langs.tsv deleted file mode 100644 index ea5a1b17b..000000000 --- a/qutebrowser/browser/webengine/langs.tsv +++ /dev/null @@ -1,42 +0,0 @@ -af-ZA Afrikaans (South Africa) af-ZA-3-0.bdic -bg-BG Bulgarian (Bulgaria) bg-BG-3-0.bdic -ca-ES Catalan (Spain) ca-ES-3-0.bdic -cs-CZ Czech (Czech Republic) cs-CZ-3-0.bdic -da-DK Danish (Denmark) da-DK-3-0.bdic -de-DE German (Germany) de-DE-3-0.bdic -el-GR Greek (Greece) el-GR-3-0.bdic -en-CA English (Canada) en-CA-7-1.bdic -en-GB English (United Kingdom) en-GB-7-1.bdic -en-US English (United States) en-US-7-1.bdic -es-ES Spanish (Spain) es-ES-3-0.bdic -et-EE Estonian (Estonia) et-EE-3-0.bdic -fa-IR Farsi (Iran) fa-IR-7-0.bdic -fo-FO Faroese (Faroe Islands) fo-FO-3-0.bdic -fr-FR French (France) fr-FR-3-0.bdic -he-IL Hebrew (Israel) he-IL-3-0.bdic -hi-IN Hindi (India) hi-IN-3-0.bdic -hr-HR Croatian (Croatia) hr-HR-3-0.bdic -hu-HU Hungarian (Hungary) hu-HU-3-0.bdic -id-ID Indonesian (Indonesia) id-ID-3-0.bdic -it-IT Italian (Italy) it-IT-3-0.bdic -ko Korean ko-3-0.bdic -lt-LT Lithuanian (Lithuania) lt-LT-3-0.bdic -lv-LV Latvian (Latvia) lv-LV-3-0.bdic -nb-NO Norwegian (Norway) nb-NO-3-0.bdic -nl-NL Dutch (Netherlands) nl-NL-3-0.bdic -pl-PL Polish (Poland) pl-PL-3-0.bdic -pt-BR Portuguese (Brazil) pt-BR-3-0.bdic -pt-PT Portuguese (Portugal) pt-PT-3-0.bdic -ro-RO Romanian (Romania) ro-RO-3-0.bdic -ru-RU Russian (Russia) ru-RU-3-0.bdic -sh Serbo-Croatian sh-3-0.bdic -sk-SK Slovak (Slovakia) sk-SK-3-0.bdic -sl-SI Slovenian (Slovenia) sl-SI-3-0.bdic -sq Albanian sq-3-0.bdic -sr Serbian sr-3-0.bdic -sv-SE Swedish (Sweden) sv-SE-3-0.bdic -ta-IN Tamil (India) ta-IN-3-0.bdic -tg-TG Tajik (Tajikistan) tg-TG-5-0.bdic -tr-TR Turkish (Turkey) tr-TR-4-0.bdic -uk-UA Ukrainian (Ukraine) uk-UA-3-0.bdic -vi-VN Vietnamese (Viet Nam) vi-VN-3-0.bdic diff --git a/qutebrowser/browser/webengine/spell.py b/qutebrowser/browser/webengine/spell.py index 5f40d189e..aaf346a79 100644 --- a/qutebrowser/browser/webengine/spell.py +++ b/qutebrowser/browser/webengine/spell.py @@ -19,132 +19,29 @@ """Installing and configuring spell-checking for QtWebEngine.""" +import glob import os -from urllib.parse import urljoin -from urllib.request import urlretrieve from PyQt5.QtCore import QLibraryInfo -repository_url = 'https://redirector.gvt1.com/edgedl/chrome/dict/' + +def dictionary_dir(): + """Return the path (str) to the QtWebEngine's dictionaries directory.""" + datapath = QLibraryInfo.location(QLibraryInfo.DataPath) + return os.path.join(datapath, 'qtwebengine_dictionaries') -class Language: +def installed_file(code): + """Return the installed dictionary for the given code. - """Dictionary language specs.""" - - def __init__(self, code, name, file): - self.code = code - self.name = name - self.file = file - - @staticmethod - def from_array(lang_array): - """Create Language object from an array. - - Args: - lang_array: an array of strings containing - the specs of the language in the following format: - [code, name, file] - """ - return Language.from_tuple(tuple(lang_array)) - - @staticmethod - def from_tuple(lang_tuple): - """Create Language object from a tuple. - - Args: - lang_tuple: a tuple of strings containing - the specs of the language in the following format: - (code, name, file) - """ - code, name, file = lang_tuple - return Language(code, name, file) - - @staticmethod - def from_tsv_string(tsv_string): - """Create Language object from a string in tab-separated values format. - - Args: - tsv_string: a string containing - the specs of the language in the following format: - "code name file" - """ - lang_array = tsv_string.split('\t') - return Language.from_array(lang_array) - - def __repr__(self): - return 'Language({}, {}, {})'.format(self.code, self.name, self.file) - - -def get_dictionary_dir(): - """Return the path to the QtWebEngine's dictionaries directory.""" - return os.path.join(QLibraryInfo.location(QLibraryInfo.DataPath), - 'qtwebengine_dictionaries') - - -def get_language_list_file(): - """Return the path to the file with the list of all available languages.""" - package_dir = os.path.dirname(os.path.abspath(__file__)) - return os.path.join(package_dir, 'langs.tsv') - - -def get_available_languages(): - """Return a list of Language objects of all available languages.""" - with open(get_language_list_file(), 'r', encoding='UTF-8') as file: - return [Language.from_tsv_string(line[:-1]) for line in file] - - -def get_installed_languages(): - """Return a list of Language objects of all installed languages.""" - if not os.path.isdir(get_dictionary_dir()): - return [] - installed_files = [os.path.basename(file) - for file in os.listdir(get_dictionary_dir())] - all_languages = get_available_languages() - return filter_languages(all_languages, installed_files, - by=lambda lang: lang.file, - fail_on_unknown=False) - - -def filter_languages(languages, selected, by=lambda lang: lang.code, - fail_on_unknown=True): - """Filter a list of languages based on an inclusion list. - - Args: - languages: a list of languages to filter - selected: a list of keys to select - by: a function returning the selection key (code by default) - fail_on_unknown: whether to raise an error if there is an unknown - key in selected + Return the filename of the installed dictionary or None + if the dictionary is not installed. """ - filtered_languages = [] - for language in languages: - if by(language) in selected: - filtered_languages.append(language) - selected.remove(by(language)) - if fail_on_unknown and selected: - unknown = ', '.join(selected) - raise ValueError('unknown languages found: {}'.format(unknown)) - return filtered_languages - - -def download_dictionary(url, dest): - urlretrieve(url, dest) - - -def install(languages): - """Install languages.""" - for lang in languages: - try: - print('Installing {}: {}'.format(lang.code, lang.name)) - lang_url = urljoin(repository_url, lang.file) - if not os.path.isdir(get_dictionary_dir()): - print('WARN: {} does not exist, creating the directory'.format( - get_dictionary_dir())) - os.makedirs(get_dictionary_dir()) - print('Downloading {}'.format(lang_url)) - download_dictionary(lang_url, os.path.join(get_dictionary_dir(), - lang.file)) - print('Done.') - except PermissionError as e: - print(e) + pathname = os.path.join(dictionary_dir(), '{}*.bdic'.format(code)) + print(pathname) + matching_dicts = glob.glob(pathname) + if matching_dicts: + with_extension = os.path.basename(matching_dicts[0]) + return os.path.splitext(with_extension)[0] + else: + return None diff --git a/qutebrowser/browser/webengine/webenginesettings.py b/qutebrowser/browser/webengine/webenginesettings.py index 1f21aa2d5..13743e0c8 100644 --- a/qutebrowser/browser/webengine/webenginesettings.py +++ b/qutebrowser/browser/webengine/webenginesettings.py @@ -36,7 +36,7 @@ from PyQt5.QtWebEngineWidgets import (QWebEngineSettings, QWebEngineProfile, QWebEngineScript) from qutebrowser.browser import shared -from qutebrowser.browser.webengine.spell import get_installed_languages +from qutebrowser.browser.webengine import spell from qutebrowser.config import config, websettings from qutebrowser.utils import utils, standarddir, javascript, qtutils, message @@ -134,20 +134,18 @@ class DictionaryLanguageSetter(DefaultProfileSetter): def __init__(self): super().__init__('setSpellCheckLanguages', default=[]) + def _find_installed(self, code): + installed_file = spell.installed_file(code) + if not installed_file: + message.warning('Language {} is not installed.'.format(code)) + return installed_file + def _set(self, value, settings=None): if settings is not None: raise ValueError("'settings' may not be set with " "DictionaryLanguageSetter!") - installed_langs = dict([(lang.code, lang.file) - for lang in get_installed_languages()]) - lang_files = [] - for lang_code in value: - if lang_code in installed_langs: - lang_files.append(installed_langs[lang_code][:-5]) - else: - message.warning('Language {} is not installed.' - .format(lang_code)) - super()._set(lang_files, settings) + filenames = [self._find_installed(code) for code in value] + super()._set([f for f in filenames if f], settings) def _init_stylesheet(profile): @@ -322,7 +320,6 @@ MAPPINGS = { 'scrolling.smooth': Attribute(QWebEngineSettings.ScrollAnimatorEnabled), - } try: @@ -334,8 +331,9 @@ except AttributeError: if qtutils.version_check('5.8'): - MAPPINGS['spell'] = DefaultProfileSetter('setSpellCheckEnabled') - MAPPINGS['spell_languages'] = DictionaryLanguageSetter() + spellcheck_setter = DefaultProfileSetter('setSpellCheckEnabled') + MAPPINGS['spellcheck.enabled'] = spellcheck_setter + MAPPINGS['spellcheck.languages'] = DictionaryLanguageSetter() if qtutils.version_check('5.9'): diff --git a/qutebrowser/config/configdata.yml b/qutebrowser/config/configdata.yml index 77b098e68..efc1d2cd4 100644 --- a/qutebrowser/config/configdata.yml +++ b/qutebrowser/config/configdata.yml @@ -977,7 +977,7 @@ scrolling.smooth: ## spell -spell: +spellcheck.enabled: type: Bool default: true desc: Enable spell checking. @@ -985,7 +985,7 @@ spell: QtWebKit: false QtWebEngine: Qt 5.8 -spell_languages: +spellcheck.languages: type: name: List valtype: @@ -1035,7 +1035,11 @@ spell_languages: - vi-VN: Vietnamese (Viet Nam) none_ok: true default: - desc: Spell checking languages. + desc: >- + Spell checking languages. + + You can check for available languages and install dictionaries using + scripts/install_dict.py. Run the script with -h/--help for instructions. backend: QtWebKit: false QtWebEngine: Qt 5.8 diff --git a/qutebrowser/misc/utilcmds.py b/qutebrowser/misc/utilcmds.py index d8cfe2366..4b6909344 100644 --- a/qutebrowser/misc/utilcmds.py +++ b/qutebrowser/misc/utilcmds.py @@ -33,7 +33,6 @@ import sip from PyQt5.QtCore import QUrl # so it's available for :debug-pyeval from PyQt5.QtWidgets import QApplication # pylint: disable=unused-import -from PyQt5.QtWebEngineWidgets import QWebEngineProfile # pylint: disable=unused-import from qutebrowser.browser import qutescheme from qutebrowser.utils import log, objreg, usertypes, message, debug, utils diff --git a/scripts/dev/update_3rdparty.py b/scripts/dev/update_3rdparty.py index 6fd6810e6..2dd65273a 100755 --- a/scripts/dev/update_3rdparty.py +++ b/scripts/dev/update_3rdparty.py @@ -27,6 +27,7 @@ import urllib.error import shutil import json import os +import sys def get_latest_pdfjs_url(): @@ -110,7 +111,25 @@ def update_ace(): urllib.request.urlcleanup() -def run(ace=False, pdfjs=True, fancy_dmg=False, pdfjs_version=None): +def test_dicts(): + """Test available dictionaries.""" + sys.path.insert(0, os.curdir) + from scripts import install_dict + from qutebrowser.config import configdata + configdata.init() + for lang in install_dict.available_languages(): + sys.stdout.write('Testing dictionary {}... '.format(lang.code)) + lang_url = urllib.parse.urljoin(install_dict.API_URL, lang.file_path) + request = urllib.request.Request(lang_url, method='HEAD') + response = urllib.request.urlopen(request) + if response.status == 200: + print('OK') + else: + print('ERROR: {}'.format(response.status)) + + +def run(ace=False, pdfjs=True, fancy_dmg=False, pdfjs_version=None, + dicts=None): """Update components based on the given arguments.""" if pdfjs: update_pdfjs(pdfjs_version) @@ -118,6 +137,8 @@ def run(ace=False, pdfjs=True, fancy_dmg=False, pdfjs_version=None): update_ace() if fancy_dmg: update_dmg_makefile() + if dicts: + test_dicts() def main(): @@ -129,9 +150,14 @@ def main(): required=False, metavar='VERSION') parser.add_argument('--fancy-dmg', help="Update fancy-dmg Makefile", action='store_true') + parser.add_argument( + '--dicts', '-d', + help='Test whether all available dictionaries ' + 'can be reached at the remote repository.', + required=False, action='store_true') args = parser.parse_args() run(ace=True, pdfjs=True, fancy_dmg=args.fancy_dmg, - pdfjs_version=args.pdfjs) + pdfjs_version=args.pdfjs, dicts=args.dicts) if __name__ == '__main__': diff --git a/scripts/install_dict.py b/scripts/install_dict.py index 7cedf8c23..a5fdabc80 100755 --- a/scripts/install_dict.py +++ b/scripts/install_dict.py @@ -24,9 +24,34 @@ Use: python -m scripts.install_dict [--list] [lang [lang [...]]] """ import argparse +import base64 +import json +import os import sys +import re +import urllib.parse +import urllib.request +import attr from qutebrowser.browser.webengine import spell +from qutebrowser.config import configdata + + +API_URL = 'https://chromium.googlesource.com/chromium/deps/hunspell_dictionaries.git/+/master/' + + +@attr.s +class Language: + """Dictionary language specs.""" + + code = attr.ib(None) + name = attr.ib(None) + file_basename = attr.ib(None) + file_extension = attr.ib('bdic') + + @property + def file_path(self): + return '.'.join([self.file_basename, self.file_extension]) def get_argparser(): @@ -46,18 +71,105 @@ def print_list(languages): print('{1}\t{0}'.format(lang.name, lang.code)) +def valid_languages(): + """Return a mapping from valid language codes to their names.""" + option = configdata.DATA['spellcheck.languages'] + return option.typ.valtype.valid_values.descriptions + + +def language_list_from_api(): + """Return a JSON with a list of available languages from Google API.""" + listurl = urllib.parse.urljoin(API_URL, '?format=JSON') + response = urllib.request.urlopen(listurl) + # TODO: what's up with the first 4 characters? + entries = json.loads(response.read().decode('utf-8')[4:])['entries'] + return entries + + +def available_languages(): + """Return a list of Language objects of all available languages.""" + lang_map = valid_languages() + api_list = language_list_from_api() + dict_re = re.compile(r""" + (?P(?P[a-z]{2}(-[A-Z]{2})?).*)\.bdic + """, re.VERBOSE) + code2file = {} + for lang in api_list: + match = dict_re.match(lang['name']) + if match is not None: + code2file[match.group('dict')] = match.group('filename') + return [ + Language(code, name, code2file[code]) + for code, name in lang_map.items() + ] + + +def download_dictionary(url, dest): + """Download a decoded dictionary file.""" + response = urllib.request.urlopen(url) + decoded = base64.decodebytes(response.read()) + with open(dest, 'bw') as dict_file: + dict_file.write(decoded) + + +def filter_languages(languages, selected): + """Filter a list of languages based on an inclusion list. + + Args: + languages: a list of languages to filter + selected: a list of keys to select + by: a function returning the selection key (code by default) + fail_on_unknown: whether to raise an error if there is an unknown + key in selected + """ + filtered_languages = [] + for language in languages: + if language.code in selected: + filtered_languages.append(language) + selected.remove(language.code) + if selected: + unknown = ', '.join(selected) + raise ValueError('unknown languages found: {}'.format(unknown)) + return filtered_languages + + +def install_lang(lang): + """Install a single lang given by the argument.""" + print('Installing {}: {}'.format(lang.code, lang.name)) + lang_url = urllib.parse.urljoin(API_URL, lang.file_path, '?format=TEXT') + if not os.path.isdir(spell.dictionary_dir()): + warn_msg = 'WARN: {} does not exist, creating the directory' + print(warn_msg.format(spell.dictionary_dir())) + os.makedirs(spell.dictionary_dir()) + print('Downloading {}'.format(lang_url)) + dest = os.path.join(spell.dictionary_dir(), lang.file_path) + download_dictionary(lang_url, dest) + print('Done.') + + +def install(languages): + """Install languages.""" + for lang in languages: + try: + install_lang(lang) + except PermissionError as e: + print(e) + + def main(): + if configdata.DATA is None: + configdata.init() parser = get_argparser() argv = sys.argv[1:] args = parser.parse_args(argv) - languages = spell.get_available_languages() + languages = available_languages() if args.list: print_list(languages) elif not args.languages: parser.print_usage() else: try: - spell.install(spell.filter_languages(languages, args.languages)) + install(filter_languages(languages, args.languages)) except ValueError as e: print(e) diff --git a/tests/end2end/conftest.py b/tests/end2end/conftest.py index 432905ff7..0b5b85505 100644 --- a/tests/end2end/conftest.py +++ b/tests/end2end/conftest.py @@ -29,7 +29,7 @@ import pstats import os.path import operator -from qutebrowser.browser.webengine.spell import get_installed_languages +from qutebrowser.browser.webengine import spell import pytest from PyQt5.QtCore import PYQT_VERSION @@ -120,18 +120,17 @@ def _get_backend_tag(tag): def _get_dictionary_tag(tag): """Handle tags like must_have_dict=en-US for BDD tests.""" - version_re = re.compile(r""" + dict_re = re.compile(r""" (?Pmust_have_dict|cannot_have_dict)=(?P[a-z]{2}-[A-Z]{2}) """, re.VERBOSE) - match = version_re.match(tag) + match = dict_re.match(tag) if not match: - #return pytest.mark.skip return None event = match.group('event') dictionary = match.group('dict') - has_dict = dictionary in [lang.code for lang in get_installed_languages()] + has_dict = spell.installed_file(dictionary) is not None if event == 'must_have_dict': return pytest.mark.skipif(not has_dict, reason=tag) elif event == 'cannot_have_dict': diff --git a/tests/end2end/features/spell.feature b/tests/end2end/features/spell.feature index 8fb996ec3..9465951c9 100644 --- a/tests/end2end/features/spell.feature +++ b/tests/end2end/features/spell.feature @@ -2,37 +2,29 @@ Feature: Setting spell checking for QtWebEngine - Background: - Given spell check languages are [] - @qtwebkit_skip @qt>=5.8 Scenario: Turn spell check on - Given spell check is off - When I run :set spell true - Then the option spell should be set to true - Then spell check is on + Given I set spellcheck.enabled to false + When I run :set spellcheck.enabled true + Then the option spellcheck.enabled should be set to true @qtwebkit_skip @qt>=5.8 Scenario: Turn spell check off - Given spell check is on - When I run :set spell false - Then the option spell should be set to false - Then spell check is off + Given I set spellcheck.enabled to true + When I run :set spellcheck.enabled false + Then the option spellcheck.enabled should be set to false @qtwebkit_skip @qt>=5.8 Scenario: Set an invalid language - When I run :set spell_languages ['invalid-language'] (invalid command) + When I run :set spellcheck.languages ['invalid-language'] (invalid command) Then the error "set: Invalid value 'invalid-language' *" should be shown - Then actual spell check languages are [] @qtwebkit_skip @qt>=5.8 @cannot_have_dict=af-ZA Scenario: Set valid but not installed language - When I run :set spell_languages ['af-ZA'] + When I run :set spellcheck.languages ['af-ZA'] Then the warning "Language af-ZA is not installed." should be shown - Then actual spell check languages are [] @qtwebkit_skip @qt>=5.8 @must_have_dict=en-US Scenario: Set valid and installed language - When I run :set spell_languages ["en-US"] - Then the option spell_languages should be set to ["en-US"] - Then actual spell check languages are ['en-US-7-1'] + When I run :set spellcheck.languages ["en-US"] + Then the option spellcheck.languages should be set to ["en-US"] diff --git a/tests/end2end/features/test_spell_bdd.py b/tests/end2end/features/test_spell_bdd.py index f6025e66b..58c3a3da8 100644 --- a/tests/end2end/features/test_spell_bdd.py +++ b/tests/end2end/features/test_spell_bdd.py @@ -20,39 +20,3 @@ import pytest_bdd as bdd bdd.scenarios('spell.feature') - - -@bdd.given(bdd.parsers.parse("spell check is {val}")) -def spellcheck_enabled_given(quteproc, val): - enabled = val == 'on' - quteproc.send_cmd(':debug-pyeval QWebEngineProfile.defaultProfile()' + - '.setSpellCheckEnabled({})'.format(enabled)) - quteproc.wait_for_load_finished('qute://pyeval') - - -@bdd.given(bdd.parsers.parse("spell check languages are {langs}")) -def spellcheck_langs_given(quteproc, langs): - quteproc.send_cmd(':debug-pyeval QWebEngineProfile.defaultProfile()' + - '.setSpellCheckLanguages({})'.format(langs)) - quteproc.wait_for_load_finished('qute://pyeval') - - -@bdd.then(bdd.parsers.parse("spell check is {val}")) -def spellcheck_enabled_then(quteproc, val): - quteproc.send_cmd(':debug-pyeval QWebEngineProfile.defaultProfile()' + - '.isSpellCheckEnabled()') - quteproc.wait_for_load_finished('qute://pyeval') - content = quteproc.get_content().strip() - if val == 'on': - assert content == 'True' - else: - assert content == 'False' - - -@bdd.then(bdd.parsers.parse("actual spell check languages are {langs}")) -def spellcheck_langs_then(quteproc, langs): - quteproc.send_cmd(':debug-pyeval QWebEngineProfile.defaultProfile()' + - '.spellCheckLanguages()') - quteproc.wait_for_load_finished('qute://pyeval') - actual_langs = quteproc.get_content().strip() - assert actual_langs == langs diff --git a/tests/unit/browser/webengine/test_spell.py b/tests/unit/browser/webengine/test_spell.py index 4a5496e20..2aebeffd5 100644 --- a/tests/unit/browser/webengine/test_spell.py +++ b/tests/unit/browser/webengine/test_spell.py @@ -18,78 +18,23 @@ # along with qutebrowser. If not, see . -from os.path import basename, join - -import pytest - from qutebrowser.browser.webengine import spell -AFRIKAANS = spell.Language('af-ZA', - 'Afrikaans (South Africa)', - 'af-ZA-3-0.bdic') -ENGLISH = spell.Language('en-US', - 'English (United States)', - 'en-US-7-1.bdic') -POLISH = spell.Language('pl-PL', - 'Polish (Poland)', - 'pl-PL-3-0.bdic') -LANGUAGE_LIST = [AFRIKAANS, ENGLISH, POLISH] +def test_installed_file_dictionary_does_not_exist(tmpdir, monkeypatch): + monkeypatch.setattr( + spell, 'dictionary_dir', lambda: '/some-non-existing-dir') + assert not spell.installed_file('en-US') -def test_get_installed_languages_empty(tmpdir, mocker): - mocker.patch('qutebrowser.browser.webengine.spell.get_dictionary_dir', - lambda: '/some-non-existing-dir') - assert spell.get_installed_languages() == [] +def test_installed_file_dictionary_not_installed(tmpdir, monkeypatch): + monkeypatch.setattr(spell, 'dictionary_dir', lambda: str(tmpdir)) + assert not spell.installed_file('en-US') -def test_get_installed_languages_non_empty(tmpdir, mocker): - mocker.patch('qutebrowser.browser.webengine.spell.get_dictionary_dir', - lambda: str(tmpdir)) - for lang in LANGUAGE_LIST: - open(join(str(tmpdir), lang.file), 'w', encoding='UTF-8').close() - for actual, expected in zip(spell.get_installed_languages(), - LANGUAGE_LIST): - assert (actual.code, actual.name, actual.file) ==\ - (expected.code, expected.name, expected.file) - - -def test_get_available_languages(): - language_list = spell.get_available_languages() - assert len(language_list) == 42 - first_lang = language_list[0] - assert (first_lang.code, first_lang.name, first_lang.file) ==\ - (AFRIKAANS.code, AFRIKAANS.name, AFRIKAANS.file) - - -def test_filter_languages(): - filtered_languages = spell.filter_languages(LANGUAGE_LIST, ['af-ZA']) - assert filtered_languages == [AFRIKAANS] - filtered_languages = spell.filter_languages(LANGUAGE_LIST, - ['pl-PL', 'en-US']) - assert filtered_languages == [ENGLISH, POLISH] - with pytest.raises(ValueError): - spell.filter_languages(LANGUAGE_LIST, ['pl-PL', 'en-GB']) - filtered_languages = spell.filter_languages(LANGUAGE_LIST, - ['pl-PL-3-0.bdic'], - by=lambda lang: lang.file) - assert filtered_languages == [POLISH] - - -def test_install(tmpdir, mocker): - mocker.patch('qutebrowser.browser.webengine.spell.get_dictionary_dir', - lambda: str(tmpdir)) - mocker.patch('qutebrowser.browser.webengine.spell.download_dictionary', - lambda url, dest: open(dest, 'w', encoding='UTF-8').close()) - spell.install(LANGUAGE_LIST) - installed_files = [basename(str(file)) for file in tmpdir.listdir()] - expected_files = [lang.file for lang in LANGUAGE_LIST] - assert sorted(installed_files) == sorted(expected_files) - - -# TODO: move somewhere to be checked before a release -#def test_available_langs(): -# for lang in spell.get_available_languages(): -# lang_url = urljoin(spell.repository_url, lang.file) -# response = head(lang_url) -# assert response.status_code == 302 +def test_installed_file_dictionary_installed(tmpdir, monkeypatch): + monkeypatch.setattr(spell, 'dictionary_dir', lambda: str(tmpdir)) + for lang_file in ['en-US-7-1.bdic', 'pl-PL-3-0.bdic']: + (tmpdir / lang_file).ensure() + assert spell.installed_file('en-US') == 'en-US-7-1' + assert spell.installed_file('pl-PL') == 'pl-PL-3-0' diff --git a/tests/unit/scripts/test_install_dict.py b/tests/unit/scripts/test_install_dict.py new file mode 100644 index 000000000..da96aacdd --- /dev/null +++ b/tests/unit/scripts/test_install_dict.py @@ -0,0 +1,71 @@ +# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: + +# Copyright 2017 Michal Siedlaczek + +# 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 . + + +import py.path # pylint: disable=no-name-in-module +import pytest + +from qutebrowser.browser.webengine import spell +from scripts import install_dict +from qutebrowser.config import configdata + +AFRIKAANS = install_dict.Language( + 'af-ZA', + 'Afrikaans (South Africa)', + 'af-ZA-3-0') +ENGLISH = install_dict.Language( + 'en-US', + 'English (United States)', + 'en-US-7-1') +POLISH = install_dict.Language( + 'pl-PL', + 'Polish (Poland)', + 'pl-PL-3-0') + +LANGUAGE_LIST = [AFRIKAANS, ENGLISH, POLISH] + + +@pytest.fixture(autouse=True) +def configdata_init(): + """Initialize configdata if needed.""" + if configdata.DATA is None: + configdata.init() + + +def test_filter_languages(): + filtered_langs = install_dict.filter_languages(LANGUAGE_LIST, ['af-ZA']) + assert filtered_langs == [AFRIKAANS] + + filtered_langs = install_dict.filter_languages( + LANGUAGE_LIST, ['pl-PL', 'en-US']) + assert filtered_langs == [ENGLISH, POLISH] + + with pytest.raises(ValueError): + install_dict.filter_languages(LANGUAGE_LIST, ['pl-PL', 'en-GB']) + + +def test_install(tmpdir, monkeypatch): + monkeypatch.setattr(spell, 'dictionary_dir', lambda: str(tmpdir)) + monkeypatch.setattr( + install_dict, 'download_dictionary', + lambda _url, dest: py.path.local(dest).ensure()) # pylint: disable=no-member + install_dict.install(LANGUAGE_LIST) + installed_files = [f.basename for f in tmpdir.listdir()] + expected_files = [lang.file_path for lang in LANGUAGE_LIST] + assert sorted(installed_files) == sorted(expected_files) From 6a486058c5f9041a07803c09e75a0c1da6f8861a Mon Sep 17 00:00:00 2001 From: Michal Siedlaczek Date: Wed, 4 Oct 2017 09:22:35 -0400 Subject: [PATCH 15/20] Review fixes --- qutebrowser/browser/webengine/spell.py | 1 - .../browser/webengine/webenginesettings.py | 2 +- qutebrowser/config/configdata.yml | 2 +- scripts/dev/check_coverage.py | 3 ++ scripts/dev/update_3rdparty.py | 12 ++++---- scripts/install_dict.py | 18 ++++++----- tests/end2end/features/misc.feature | 12 ++++++++ tests/end2end/features/spell.feature | 30 ------------------- tests/end2end/features/test_spell_bdd.py | 22 -------------- tests/unit/scripts/test_install_dict.py | 2 +- 10 files changed, 36 insertions(+), 68 deletions(-) delete mode 100644 tests/end2end/features/spell.feature delete mode 100644 tests/end2end/features/test_spell_bdd.py diff --git a/qutebrowser/browser/webengine/spell.py b/qutebrowser/browser/webengine/spell.py index aaf346a79..a915b1d52 100644 --- a/qutebrowser/browser/webengine/spell.py +++ b/qutebrowser/browser/webengine/spell.py @@ -38,7 +38,6 @@ def installed_file(code): if the dictionary is not installed. """ pathname = os.path.join(dictionary_dir(), '{}*.bdic'.format(code)) - print(pathname) matching_dicts = glob.glob(pathname) if matching_dicts: with_extension = os.path.basename(matching_dicts[0]) diff --git a/qutebrowser/browser/webengine/webenginesettings.py b/qutebrowser/browser/webengine/webenginesettings.py index 13743e0c8..c2ae7a018 100644 --- a/qutebrowser/browser/webengine/webenginesettings.py +++ b/qutebrowser/browser/webengine/webenginesettings.py @@ -27,9 +27,9 @@ Module attributes: constants. """ +import os import ctypes import ctypes.util -import os from PyQt5.QtGui import QFont from PyQt5.QtWebEngineWidgets import (QWebEngineSettings, QWebEngineProfile, diff --git a/qutebrowser/config/configdata.yml b/qutebrowser/config/configdata.yml index efc1d2cd4..d051b3e8a 100644 --- a/qutebrowser/config/configdata.yml +++ b/qutebrowser/config/configdata.yml @@ -1034,7 +1034,7 @@ spellcheck.languages: - uk-UA: Ukrainian (Ukraine) - vi-VN: Vietnamese (Viet Nam) none_ok: true - default: + default: [] desc: >- Spell checking languages. diff --git a/scripts/dev/check_coverage.py b/scripts/dev/check_coverage.py index aa5072536..e60ee78fd 100644 --- a/scripts/dev/check_coverage.py +++ b/scripts/dev/check_coverage.py @@ -172,6 +172,9 @@ PERFECT_FILES = [ ('tests/unit/completion/test_listcategory.py', 'completion/models/listcategory.py'), + ('tests/unit/browser/webengine/test_spell.py', + 'browser/webengine/spell.py'), + ] diff --git a/scripts/dev/update_3rdparty.py b/scripts/dev/update_3rdparty.py index 2dd65273a..a72e9368e 100755 --- a/scripts/dev/update_3rdparty.py +++ b/scripts/dev/update_3rdparty.py @@ -29,6 +29,11 @@ import json import os import sys +sys.path.insert( + 0, os.path.join(os.path.dirname(__file__), os.pardir, os.pardir)) +from scripts import install_dict +from qutebrowser.config import configdata + def get_latest_pdfjs_url(): """Get the URL of the latest pdf.js prebuilt package. @@ -113,12 +118,9 @@ def update_ace(): def test_dicts(): """Test available dictionaries.""" - sys.path.insert(0, os.curdir) - from scripts import install_dict - from qutebrowser.config import configdata configdata.init() for lang in install_dict.available_languages(): - sys.stdout.write('Testing dictionary {}... '.format(lang.code)) + print('Testing dictionary {}... '.format(lang.code), end='') lang_url = urllib.parse.urljoin(install_dict.API_URL, lang.file_path) request = urllib.request.Request(lang_url, method='HEAD') response = urllib.request.urlopen(request) @@ -129,7 +131,7 @@ def test_dicts(): def run(ace=False, pdfjs=True, fancy_dmg=False, pdfjs_version=None, - dicts=None): + dicts=False): """Update components based on the given arguments.""" if pdfjs: update_pdfjs(pdfjs_version) diff --git a/scripts/install_dict.py b/scripts/install_dict.py index a5fdabc80..13839d150 100755 --- a/scripts/install_dict.py +++ b/scripts/install_dict.py @@ -40,6 +40,14 @@ from qutebrowser.config import configdata API_URL = 'https://chromium.googlesource.com/chromium/deps/hunspell_dictionaries.git/+/master/' +class InvalidLanguageError(Exception): + """Raised when requested invalid languages.""" + + def __init__(self, invalid_langs): + msg = 'invalid languages: {}'.format(', '.join(invalid_langs)) + super(InvalidLanguageError, self).__init__(msg) + + @attr.s class Language: """Dictionary language specs.""" @@ -68,7 +76,7 @@ def get_argparser(): def print_list(languages): for lang in languages: - print('{1}\t{0}'.format(lang.name, lang.code)) + print(lang.code, lang.name, sep='\t') def valid_languages(): @@ -118,9 +126,6 @@ def filter_languages(languages, selected): Args: languages: a list of languages to filter selected: a list of keys to select - by: a function returning the selection key (code by default) - fail_on_unknown: whether to raise an error if there is an unknown - key in selected """ filtered_languages = [] for language in languages: @@ -128,8 +133,7 @@ def filter_languages(languages, selected): filtered_languages.append(language) selected.remove(language.code) if selected: - unknown = ', '.join(selected) - raise ValueError('unknown languages found: {}'.format(unknown)) + raise InvalidLanguageError(selected) return filtered_languages @@ -170,7 +174,7 @@ def main(): else: try: install(filter_languages(languages, args.languages)) - except ValueError as e: + except InvalidLanguageError as e: print(e) diff --git a/tests/end2end/features/misc.feature b/tests/end2end/features/misc.feature index 3d23033f5..110401048 100644 --- a/tests/end2end/features/misc.feature +++ b/tests/end2end/features/misc.feature @@ -526,3 +526,15 @@ Feature: Various utility commands. And I wait for "Renderer process was killed" in the log And I open data/numbers/3.txt Then no crash should happen + + ## Spellcheck + + @qtwebkit_skip @qt>=5.8 @cannot_have_dict=af-ZA + Scenario: Set valid but not installed language + When I run :set spellcheck.languages ['af-ZA'] + Then the warning "Language af-ZA is not installed." should be shown + + @qtwebkit_skip @qt>=5.8 @must_have_dict=en-US + Scenario: Set valid and installed language + When I run :set spellcheck.languages ["en-US"] + Then the option spellcheck.languages should be set to ["en-US"] diff --git a/tests/end2end/features/spell.feature b/tests/end2end/features/spell.feature deleted file mode 100644 index 9465951c9..000000000 --- a/tests/end2end/features/spell.feature +++ /dev/null @@ -1,30 +0,0 @@ -# vim: ft=cucumber fileencoding=utf-8 sts=4 sw=4 et: - -Feature: Setting spell checking for QtWebEngine - - @qtwebkit_skip @qt>=5.8 - Scenario: Turn spell check on - Given I set spellcheck.enabled to false - When I run :set spellcheck.enabled true - Then the option spellcheck.enabled should be set to true - - @qtwebkit_skip @qt>=5.8 - Scenario: Turn spell check off - Given I set spellcheck.enabled to true - When I run :set spellcheck.enabled false - Then the option spellcheck.enabled should be set to false - - @qtwebkit_skip @qt>=5.8 - Scenario: Set an invalid language - When I run :set spellcheck.languages ['invalid-language'] (invalid command) - Then the error "set: Invalid value 'invalid-language' *" should be shown - - @qtwebkit_skip @qt>=5.8 @cannot_have_dict=af-ZA - Scenario: Set valid but not installed language - When I run :set spellcheck.languages ['af-ZA'] - Then the warning "Language af-ZA is not installed." should be shown - - @qtwebkit_skip @qt>=5.8 @must_have_dict=en-US - Scenario: Set valid and installed language - When I run :set spellcheck.languages ["en-US"] - Then the option spellcheck.languages should be set to ["en-US"] diff --git a/tests/end2end/features/test_spell_bdd.py b/tests/end2end/features/test_spell_bdd.py deleted file mode 100644 index 58c3a3da8..000000000 --- a/tests/end2end/features/test_spell_bdd.py +++ /dev/null @@ -1,22 +0,0 @@ -# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: - -# Copyright 2017 Michał Siedlaczek -# -# 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 . - -import pytest_bdd as bdd - -bdd.scenarios('spell.feature') diff --git a/tests/unit/scripts/test_install_dict.py b/tests/unit/scripts/test_install_dict.py index da96aacdd..4493d9e9a 100644 --- a/tests/unit/scripts/test_install_dict.py +++ b/tests/unit/scripts/test_install_dict.py @@ -56,7 +56,7 @@ def test_filter_languages(): LANGUAGE_LIST, ['pl-PL', 'en-US']) assert filtered_langs == [ENGLISH, POLISH] - with pytest.raises(ValueError): + with pytest.raises(install_dict.InvalidLanguageError): install_dict.filter_languages(LANGUAGE_LIST, ['pl-PL', 'en-GB']) From c5d695b59eda95e021a4402534fbc68887ff7f96 Mon Sep 17 00:00:00 2001 From: Michal Siedlaczek Date: Wed, 4 Oct 2017 09:54:59 -0400 Subject: [PATCH 16/20] Remove spellcheck.enabled option. --- qutebrowser/browser/webengine/webenginesettings.py | 2 -- qutebrowser/config/configdata.yml | 10 +--------- 2 files changed, 1 insertion(+), 11 deletions(-) diff --git a/qutebrowser/browser/webengine/webenginesettings.py b/qutebrowser/browser/webengine/webenginesettings.py index 4248be03c..7783cd38e 100644 --- a/qutebrowser/browser/webengine/webenginesettings.py +++ b/qutebrowser/browser/webengine/webenginesettings.py @@ -331,8 +331,6 @@ except AttributeError: if qtutils.version_check('5.8'): - spellcheck_setter = DefaultProfileSetter('setSpellCheckEnabled') - MAPPINGS['spellcheck.enabled'] = spellcheck_setter MAPPINGS['spellcheck.languages'] = DictionaryLanguageSetter() diff --git a/qutebrowser/config/configdata.yml b/qutebrowser/config/configdata.yml index 48e6fab6d..98fa35194 100644 --- a/qutebrowser/config/configdata.yml +++ b/qutebrowser/config/configdata.yml @@ -992,15 +992,7 @@ scrolling.smooth: Note smooth scrolling does not work with the `:scroll-px` command. -## spell - -spellcheck.enabled: - type: Bool - default: true - desc: Enable spell checking. - backend: - QtWebKit: false - QtWebEngine: Qt 5.8 +## spellcheck spellcheck.languages: type: From 6d9f04355cbde05d9bd2d5e1f11b5b0a3c627dcd Mon Sep 17 00:00:00 2001 From: Michal Siedlaczek Date: Wed, 4 Oct 2017 10:06:14 -0400 Subject: [PATCH 17/20] Strip JSON response from Google API of the 5-byte prefix Read more here: * https://github.com/google/gitiles/issues/22 * https://github.com/google/gitiles/issues/82 --- scripts/install_dict.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/scripts/install_dict.py b/scripts/install_dict.py index 13839d150..9590d07d8 100755 --- a/scripts/install_dict.py +++ b/scripts/install_dict.py @@ -89,8 +89,11 @@ def language_list_from_api(): """Return a JSON with a list of available languages from Google API.""" listurl = urllib.parse.urljoin(API_URL, '?format=JSON') response = urllib.request.urlopen(listurl) - # TODO: what's up with the first 4 characters? - entries = json.loads(response.read().decode('utf-8')[4:])['entries'] + # A special 5-byte prefix must be stripped from the response content + # See: https://github.com/google/gitiles/issues/22 + # https://github.com/google/gitiles/issues/82 + json_content = response.read()[5:] + entries = json.loads(json_content.decode('utf-8'))['entries'] return entries From fd9a5fa334b4bcbfa9306b281761b54a691ed1dc Mon Sep 17 00:00:00 2001 From: Michal Siedlaczek Date: Thu, 5 Oct 2017 09:56:13 -0400 Subject: [PATCH 18/20] Style fixes --- scripts/install_dict.py | 9 +++++++-- tests/end2end/features/misc.feature | 2 +- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/scripts/install_dict.py b/scripts/install_dict.py index 9590d07d8..ce5bc1bdb 100755 --- a/scripts/install_dict.py +++ b/scripts/install_dict.py @@ -31,8 +31,10 @@ import sys import re import urllib.parse import urllib.request + import attr +sys.path.insert(0, os.path.join(os.path.dirname(__file__), os.pardir)) from qutebrowser.browser.webengine import spell from qutebrowser.config import configdata @@ -41,15 +43,17 @@ API_URL = 'https://chromium.googlesource.com/chromium/deps/hunspell_dictionaries class InvalidLanguageError(Exception): - """Raised when requested invalid languages.""" + + """Raised when requesting invalid languages.""" def __init__(self, invalid_langs): msg = 'invalid languages: {}'.format(', '.join(invalid_langs)) - super(InvalidLanguageError, self).__init__(msg) + super().__init__(msg) @attr.s class Language: + """Dictionary language specs.""" code = attr.ib(None) @@ -161,6 +165,7 @@ def install(languages): install_lang(lang) except PermissionError as e: print(e) + sys.exit(1) def main(): diff --git a/tests/end2end/features/misc.feature b/tests/end2end/features/misc.feature index b40eb815f..2f5bb1fcd 100644 --- a/tests/end2end/features/misc.feature +++ b/tests/end2end/features/misc.feature @@ -551,4 +551,4 @@ Feature: Various utility commands. @qtwebkit_skip @qt>=5.8 @must_have_dict=en-US Scenario: Set valid and installed language When I run :set spellcheck.languages ["en-US"] - Then the option spellcheck.languages should be set to ["en-US"] \ No newline at end of file + Then the option spellcheck.languages should be set to ["en-US"] From 4bac2f3e444e53e68b9c85161cb72aa598b9c561 Mon Sep 17 00:00:00 2001 From: Michal Siedlaczek Date: Fri, 6 Oct 2017 07:58:26 -0400 Subject: [PATCH 19/20] Initialize profiles with spellchecking turn on by default. --- qutebrowser/browser/webengine/webenginesettings.py | 8 +++++++- scripts/install_dict.py | 2 +- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/qutebrowser/browser/webengine/webenginesettings.py b/qutebrowser/browser/webengine/webenginesettings.py index 7783cd38e..edae90130 100644 --- a/qutebrowser/browser/webengine/webenginesettings.py +++ b/qutebrowser/browser/webengine/webenginesettings.py @@ -137,7 +137,9 @@ class DictionaryLanguageSetter(DefaultProfileSetter): def _find_installed(self, code): installed_file = spell.installed_file(code) if not installed_file: - message.warning('Language {} is not installed.'.format(code)) + message.warning( + 'Language {} is not installed - see scripts/install_dict.py ' + 'in qutebrowser\'s sources'.format(code)) return installed_file def _set(self, value, settings=None): @@ -219,6 +221,10 @@ def _init_profiles(): _init_stylesheet(private_profile) _set_http_headers(private_profile) + if qtutils.version_check('5.8'): + default_profile.setSpellCheckEnabled(True) + private_profile.setSpellCheckEnabled(True) + def init(args): """Initialize the global QWebSettings.""" diff --git a/scripts/install_dict.py b/scripts/install_dict.py index ce5bc1bdb..7ce333848 100755 --- a/scripts/install_dict.py +++ b/scripts/install_dict.py @@ -149,7 +149,7 @@ def install_lang(lang): print('Installing {}: {}'.format(lang.code, lang.name)) lang_url = urllib.parse.urljoin(API_URL, lang.file_path, '?format=TEXT') if not os.path.isdir(spell.dictionary_dir()): - warn_msg = 'WARN: {} does not exist, creating the directory' + warn_msg = '{} does not exist, creating the directory' print(warn_msg.format(spell.dictionary_dir())) os.makedirs(spell.dictionary_dir()) print('Downloading {}'.format(lang_url)) From 00666feaf6580815b40045ad784681c9bf2ef989 Mon Sep 17 00:00:00 2001 From: Michal Siedlaczek Date: Sat, 7 Oct 2017 15:54:22 -0400 Subject: [PATCH 20/20] FAQ update for spellcheck & test fix --- doc/faq.asciidoc | 17 ++++++++--------- tests/end2end/features/misc.feature | 2 +- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/doc/faq.asciidoc b/doc/faq.asciidoc index c46286950..ba4364720 100644 --- a/doc/faq.asciidoc +++ b/doc/faq.asciidoc @@ -125,9 +125,9 @@ When using quickmark, you can give them all names, like without having to remember the exact website title or address. How do I use spell checking?:: - Qutebrowser's support for spell checking is somewhat limited at the moment - (see https://github.com/qutebrowser/qutebrowser/issues/700[#700]), but it - can be done. + Configuring spell checking in Qutebrowser depends on the backend in use + (see https://github.com/qutebrowser/qutebrowser/issues/700[#700] for + a more detailed discussion). + For QtWebKit: @@ -145,12 +145,11 @@ For QtWebKit: + For QtWebEngine: -. Not yet supported unfortunately :-( + - Adding it shouldn't be too hard though, since QtWebEngine 5.8 added an API for - this (see - https://github.com/qutebrowser/qutebrowser/issues/700#issuecomment-290780706[this - comment for a basic example]), so what are you waiting for and why aren't you - hacking qutebrowser yet? +. Make sure your version of PyQt is 5.8 or higher. +. Use `install_dict.py` script to install dictionaries. + Run the script with `-h` for the parameter description. +. Set `spellcheck.languages` to the desired list of languages, e.g.: + `:set spellcheck.languages "['en-US', 'pl-PL']"` How do I use Tor with qutebrowser?:: Start tor on your machine, and do `:set network proxy socks://localhost:9050/` diff --git a/tests/end2end/features/misc.feature b/tests/end2end/features/misc.feature index 2f5bb1fcd..8750cb812 100644 --- a/tests/end2end/features/misc.feature +++ b/tests/end2end/features/misc.feature @@ -546,7 +546,7 @@ Feature: Various utility commands. @qtwebkit_skip @qt>=5.8 @cannot_have_dict=af-ZA Scenario: Set valid but not installed language When I run :set spellcheck.languages ['af-ZA'] - Then the warning "Language af-ZA is not installed." should be shown + Then the warning "Language af-ZA is not installed *" should be shown @qtwebkit_skip @qt>=5.8 @must_have_dict=en-US Scenario: Set valid and installed language