Support updating dictionaries and removing old versions.

This commit is contained in:
Michal Siedlaczek 2017-11-04 18:16:05 -04:00
parent 2dc0115c81
commit 3ac2cfdf73
9 changed files with 474 additions and 287 deletions

View File

@ -30,10 +30,11 @@ from PyQt5.QtCore import QLibraryInfo
def version(filename):
"""Extract the version number from the dictionary file name."""
version_re = re.compile(r"""
.+(?P<version>[0-9]+-[0-9]+)\.bdic
.+-(?P<version>[0-9]+-[0-9]+?)\.bdic
""", re.VERBOSE)
match = version_re.match(filename)
assert match is not None, 'the given dictionary file name is malformed'
assert match is not None, \
'the given dictionary file name is malformed: {}'.format(filename)
return [int(n) for n in match.group('version').split('-')]
@ -43,18 +44,23 @@ def dictionary_dir():
return os.path.join(datapath, 'qtwebengine_dictionaries')
def installed_file(code):
def local_files(code):
"""Return all installed dictionaries for the given code."""
pathname = os.path.join(dictionary_dir(), '{}*.bdic'.format(code))
matching_dicts = glob.glob(pathname)
files = []
for matching_dict in sorted(matching_dicts, key=version, reverse=True):
filename = os.path.basename(matching_dict)
log.config.debug('Found file for dict {}: {}'.format(code, filename))
files.append(filename)
return files
def local_filename(code):
"""Return the newest installed dictionary for the given code.
Return the filename of the installed dictionary with the highest version
number or None if the dictionary is not installed.
"""
pathname = os.path.join(dictionary_dir(), '{}*.bdic'.format(code))
matching_dicts = glob.glob(pathname)
if matching_dicts:
log.config.debug('Found files for dict {}: {}'.format(code, matching_dicts))
matching_dicts = sorted(matching_dicts, key=version)
with_extension = os.path.basename(matching_dicts[0])
return os.path.splitext(with_extension)[0]
else:
return None
all_installed = local_files(code)
return os.path.splitext(all_installed[0])[0] if all_installed else None

View File

@ -134,12 +134,12 @@ class DictionaryLanguageSetter(DefaultProfileSetter):
super().__init__('setSpellCheckLanguages', default=[])
def _find_installed(self, code):
installed_file = spell.installed_file(code)
if not installed_file:
local_filename = spell.local_filename(code)
if not local_filename:
message.warning(
"Language {} is not installed - see scripts/install_dict.py "
"Language {} is not installed - see scripts/dict.py "
"in qutebrowser's sources".format(code))
return installed_file
return local_filename
def _set(self, value, settings=None):
if settings is not None:

View File

@ -31,7 +31,7 @@ import sys
sys.path.insert(
0, os.path.join(os.path.dirname(__file__), os.pardir, os.pardir))
from scripts import install_dict
from scripts import dictcli
from qutebrowser.config import configdata
@ -119,9 +119,9 @@ def update_ace():
def test_dicts():
"""Test available dictionaries."""
configdata.init()
for lang in install_dict.available_languages():
for lang in dictcli.available_languages():
print('Testing dictionary {}... '.format(lang.code), end='')
lang_url = urllib.parse.urljoin(install_dict.API_URL, lang.file_path)
lang_url = urllib.parse.urljoin(dictcli.API_URL, lang.remote_path)
request = urllib.request.Request(lang_url, method='HEAD')
response = urllib.request.urlopen(request)
if response.status == 200:

282
scripts/dictcli.py Executable file
View File

@ -0,0 +1,282 @@
#!/usr/bin/env python3
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2017 Michal Siedlaczek <michal.siedlaczek@gmail.com>
# 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 <http://www.gnu.org/licenses/>.
"""A script installing Hunspell dictionaries.
Use: python -m scripts.dictcli [-h] {list,update,remove-old,install} ...
"""
import argparse
import base64
import json
import os
import sys
import re
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
API_URL = 'https://chromium.googlesource.com/chromium/deps/hunspell_dictionaries.git/+/master/'
class InvalidLanguageError(Exception):
"""Raised when requesting invalid languages."""
def __init__(self, invalid_langs):
msg = 'invalid languages: {}'.format(', '.join(invalid_langs))
super().__init__(msg)
@attr.s
class Language:
"""Dictionary language specs."""
code = attr.ib()
name = attr.ib()
remote_filename = attr.ib()
local_filename = attr.ib(default=None)
_file_extension = attr.ib('bdic', init=False)
def __attrs_post_init__(self):
if self.local_filename is None:
self.local_filename = spell.local_filename(self.code)
@property
def remote_path(self):
"""Resolve the filename with extension the remote dictionary."""
return '.'.join([self.remote_filename, self._file_extension])
@property
def local_path(self):
"""Resolve the filename with extension the local dictionary."""
if self.local_filename is None:
return None
return '.'.join([self.local_filename, self._file_extension])
@property
def remote_version(self):
"""Resolve the version of the local dictionary."""
return spell.version(self.remote_path)
@property
def local_version(self):
"""Resolve the version of the local dictionary."""
local_path = self.local_path
if local_path is None:
return None
return spell.version(local_path)
def get_argparser():
"""Get the argparse parser."""
desc = 'Install and manage Hunspell dictionaries for QtWebEngine.'
parser = argparse.ArgumentParser(prog='dictcli',
description=desc)
subparsers = parser.add_subparsers(help='Command', dest='cmd')
subparsers.add_parser('list',
help='Display the list of available languages.')
subparsers.add_parser('update',
help='Update dictionaries')
subparsers.add_parser('remove-old',
help='Remove old versions of dictionaries.')
install_parser = subparsers.add_parser('install',
help='Install dictionaries')
install_parser.add_argument('language',
nargs='*', help="A list of languages to install.")
return parser
def version_str(version):
return '.'.join(str(n) for n in version)
def print_list(languages):
"""Print the list of available languages."""
pat = '{:<7}{:<26}{:<8}{:<5}'
print(pat.format('Code', 'Name', 'Version', 'Installed'))
for lang in languages:
remote_version = version_str(lang.remote_version)
local_version = '-'
if lang.local_version is not None:
local_version = version_str(lang.local_version)
if lang.local_version < lang.remote_version:
local_version += ' - update available!'
print(pat.format(lang.code, lang.name, remote_version, local_version))
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 parse_entry(entry):
"""Parse an entry from the remote API."""
dict_re = re.compile(r"""
(?P<filename>(?P<code>[a-z]{2}(-[A-Z]{2})?).*)\.bdic
""", re.VERBOSE)
match = dict_re.match(entry['name'])
if match is not None:
return match.group('code'), match.group('filename')
else:
return None
def language_list_from_api():
"""Return a JSON with a list of available languages from Google API."""
listurl = API_URL + '?format=JSON'
response = urllib.request.urlopen(listurl)
# 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']
parsed_entries = [parse_entry(entry) for entry in entries]
return [entry for entry in parsed_entries if entry is not None]
def latest_yet(code2file, code, filename):
"""Determine wether the latest version so far."""
if code not in code2file:
return True
return spell.version(code2file[code]) < spell.version(filename)
def available_languages():
"""Return a list of Language objects of all available languages."""
lang_map = valid_languages()
api_list = language_list_from_api()
code2file = {}
for code, filename in api_list:
if latest_yet(code2file, code, filename):
code2file[code] = filename
print(code2file)
return [
Language(code, name, code2file[code])
for code, name in lang_map.items()
if code in code2file
]
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
"""
filtered_languages = []
for language in languages:
if language.code in selected:
filtered_languages.append(language)
selected.remove(language.code)
if selected:
raise InvalidLanguageError(selected)
return filtered_languages
def install_lang(lang):
"""Install a single lang given by the argument."""
lang_url = API_URL + lang.remote_path + '?format=TEXT'
if not os.path.isdir(spell.dictionary_dir()):
msg = '{} does not exist, creating the directory'
print(msg.format(spell.dictionary_dir()))
os.makedirs(spell.dictionary_dir())
print('Downloading {}'.format(lang_url))
dest = os.path.join(spell.dictionary_dir(), lang.remote_path)
download_dictionary(lang_url, dest)
print('Done.')
def install(languages):
"""Install languages."""
for lang in languages:
try:
print('Installing {}: {}'.format(lang.code, lang.name))
install_lang(lang)
except PermissionError as e:
print(e)
sys.exit(1)
def update(languages):
installed = [lang for lang in languages if lang.local_version is not None]
for lang in installed:
if lang.local_version < lang.remote_version:
print('Upgrading {} from {} to {}'.format(
lang.code,
version_str(lang.local_version),
version_str(lang.remote_version)))
install_lang(lang)
def remove_old(languages):
installed = [lang for lang in languages if lang.local_version is not None]
for lang in installed:
local_files = spell.local_files(lang.code)
for old_file in local_files[1:]:
os.remove(os.path.join(spell.dictionary_dir(), old_file))
def main():
if configdata.DATA is None:
configdata.init()
parser = get_argparser()
argv = sys.argv[1:]
args = parser.parse_args(argv)
languages = available_languages()
if args.cmd is None:
parser.print_usage()
exit(1)
elif args.cmd == 'list':
print_list(languages)
elif args.cmd == 'update':
update(languages)
elif args.cmd == 'remove-old':
remove_old(languages)
elif not args.language:
print('You must provide a list of languages to install.')
exit(1)
else:
try:
install(filter_languages(languages, args.language))
except InvalidLanguageError as e:
print(e)
if __name__ == '__main__':
main()

View File

@ -1,189 +0,0 @@
#!/usr/bin/env python3
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2017 Michal Siedlaczek <michal.siedlaczek@gmail.com>
# 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 <http://www.gnu.org/licenses/>.
"""A script installing Hunspell dictionaries.
Use: python -m scripts.install_dict [--list] [lang [lang [...]]]
"""
import argparse
import base64
import json
import os
import sys
import re
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
API_URL = 'https://chromium.googlesource.com/chromium/deps/hunspell_dictionaries.git/+/master/'
class InvalidLanguageError(Exception):
"""Raised when requesting invalid languages."""
def __init__(self, invalid_langs):
msg = 'invalid languages: {}'.format(', '.join(invalid_langs))
super().__init__(msg)
@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():
"""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(lang.code, lang.name, sep='\t')
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 = API_URL + '?format=JSON'
response = urllib.request.urlopen(listurl)
# 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
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<filename>(?P<dict>[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
"""
filtered_languages = []
for language in languages:
if language.code in selected:
filtered_languages.append(language)
selected.remove(language.code)
if selected:
raise InvalidLanguageError(selected)
return filtered_languages
def install_lang(lang):
"""Install a single lang given by the argument."""
print('Installing {}: {}'.format(lang.code, lang.name))
lang_url = API_URL + lang.file_path + '?format=TEXT'
if not os.path.isdir(spell.dictionary_dir()):
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))
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)
sys.exit(1)
def main():
if configdata.DATA is None:
configdata.init()
parser = get_argparser()
argv = sys.argv[1:]
args = parser.parse_args(argv)
languages = available_languages()
if args.list:
print_list(languages)
elif not args.languages:
parser.print_usage()
else:
try:
install(filter_languages(languages, args.languages))
except InvalidLanguageError as e:
print(e)
if __name__ == '__main__':
main()

View File

@ -131,7 +131,7 @@ def _get_dictionary_tag(tag):
event = match.group('event')
dictionary = match.group('dict')
has_dict = spell.installed_file(dictionary) is not None
has_dict = spell.local_filename(dictionary) is not None
if event == 'must_have_dict':
return pytest.mark.skipif(not has_dict, reason=tag)
elif event == 'cannot_have_dict':

View File

@ -26,20 +26,20 @@ def test_version():
assert spell.version('pl-PL-3-0.bdic') == [3, 0]
def test_installed_file_dictionary_does_not_exist(tmpdir, monkeypatch):
def test_local_filename_dictionary_does_not_exist(tmpdir, monkeypatch):
monkeypatch.setattr(
spell, 'dictionary_dir', lambda: '/some-non-existing-dir')
assert not spell.installed_file('en-US')
assert not spell.local_filename('en-US')
def test_installed_file_dictionary_not_installed(tmpdir, monkeypatch):
def test_local_filename_dictionary_not_installed(tmpdir, monkeypatch):
monkeypatch.setattr(spell, 'dictionary_dir', lambda: str(tmpdir))
assert not spell.installed_file('en-US')
assert not spell.local_filename('en-US')
def test_installed_file_dictionary_installed(tmpdir, monkeypatch):
def test_local_filename_dictionary_installed(tmpdir, monkeypatch):
monkeypatch.setattr(spell, 'dictionary_dir', lambda: str(tmpdir))
for lang_file in ['en-US-11-0.bdic', 'en-US-7-1.bdic', 'pl-PL-3-0.bdic']:
(tmpdir / lang_file).ensure()
assert spell.installed_file('en-US') == 'en-US-11-0'
assert spell.installed_file('pl-PL') == 'pl-PL-3-0'
assert spell.local_filename('en-US') == 'en-US-11-0'
assert spell.local_filename('pl-PL') == 'pl-PL-3-0'

View File

@ -0,0 +1,159 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2017 Michal Siedlaczek <michal.siedlaczek@gmail.com>
# 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 <http://www.gnu.org/licenses/>.
import py.path # pylint: disable=no-name-in-module
import pytest
from qutebrowser.browser.webengine import spell
from scripts import dictcli
from qutebrowser.config import configdata
def afrikaans():
return dictcli.Language(
'af-ZA',
'Afrikaans (South Africa)',
'af-ZA-3-0')
def english():
return dictcli.Language(
'en-US',
'English (United States)',
'en-US-7-1')
def polish():
return dictcli.Language(
'pl-PL',
'Polish (Poland)',
'pl-PL-3-0')
def langs():
return [afrikaans(), english(), polish()]
@pytest.fixture(autouse=True)
def configdata_init():
"""Initialize configdata if needed."""
if configdata.DATA is None:
configdata.init()
def test_language(tmpdir, monkeypatch):
monkeypatch.setattr(spell, 'dictionary_dir', lambda: str(tmpdir))
(tmpdir / 'pl-PL-2-0.bdic').ensure()
assert english().local_filename is None
assert english().local_path is None
assert polish()
def test_parse_entry():
assert dictcli.parse_entry({'name': 'en-US-7-1.bdic'}) == \
('en-US', 'en-US-7-1')
def test_latest_yet():
code2file = {'en-US': 'en-US-7-1.bdic'}
assert not dictcli.latest_yet(code2file, 'en-US', 'en-US-7-0.bdic')
assert not dictcli.latest_yet(code2file, 'en-US', 'en-US-7-1.bdic')
assert dictcli.latest_yet(code2file, 'en-US', 'en-US-8-0.bdic')
def test_available_languages(tmpdir, monkeypatch):
monkeypatch.setattr(spell, 'dictionary_dir', lambda: str(tmpdir))
for f in ['pl-PL-2-0.bdic', english().remote_path]:
(tmpdir / f).ensure()
monkeypatch.setattr(dictcli, 'language_list_from_api', lambda: [
(lang.code, lang.remote_filename) for lang in langs()
])
assert dictcli.available_languages() == [
dictcli.Language(
'af-ZA', 'Afrikaans (South Africa)',
'af-ZA-3-0', None),
dictcli.Language(
'en-US', 'English (United States)',
'en-US-7-1', 'en-US-7-1'),
dictcli.Language(
'pl-PL', 'Polish (Poland)',
'pl-PL-3-0', 'pl-PL-2-0')
]
def test_filter_languages():
filtered_langs = dictcli.filter_languages(langs(), ['af-ZA'])
assert filtered_langs == [afrikaans()]
filtered_langs = dictcli.filter_languages(langs(), ['pl-PL', 'en-US'])
assert filtered_langs == [english(), polish()]
with pytest.raises(dictcli.InvalidLanguageError):
dictcli.filter_languages(langs(), ['pl-PL', 'en-GB'])
def test_install(tmpdir, monkeypatch):
# given
monkeypatch.setattr(spell, 'dictionary_dir', lambda: str(tmpdir))
monkeypatch.setattr(
dictcli, 'download_dictionary',
lambda _url, dest: py.path.local(dest).ensure()) # pylint: disable=no-member
# when
dictcli.install(langs())
# then
installed_files = [f.basename for f in tmpdir.listdir()]
expected_files = [lang.remote_path for lang in langs()]
assert sorted(installed_files) == sorted(expected_files)
def test_update(tmpdir, monkeypatch):
# given
monkeypatch.setattr(spell, 'dictionary_dir', lambda: str(tmpdir))
monkeypatch.setattr(
dictcli, 'download_dictionary',
lambda _url, dest: py.path.local(dest).ensure()) # pylint: disable=no-member
(tmpdir / 'pl-PL-2-0.bdic').ensure()
assert polish().local_version < polish().remote_version
# when
dictcli.update(langs())
# then
assert polish().local_version == polish().remote_version
def test_remove_old(tmpdir, monkeypatch):
# given
monkeypatch.setattr(spell, 'dictionary_dir', lambda: str(tmpdir))
monkeypatch.setattr(
dictcli, 'download_dictionary',
lambda _url, dest: py.path.local(dest).ensure()) # pylint: disable=no-member
for f in ['pl-PL-2-0.bdic', polish().remote_path, english().remote_path]:
(tmpdir / f).ensure()
# when
dictcli.remove_old(langs())
# then
installed_files = [f.basename for f in tmpdir.listdir()]
expected_files = [polish().remote_path, english().remote_path]
assert sorted(installed_files) == sorted(expected_files)

View File

@ -1,71 +0,0 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2017 Michal Siedlaczek <michal.siedlaczek@gmail.com>
# 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 <http://www.gnu.org/licenses/>.
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(install_dict.InvalidLanguageError):
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)