spellchecker: Improve support for symspellpy

Add support for 6.3.8 which has delete_dictionary_entry and do not use gzipped
pickle. Also give higher priority to symspellpy vs pyspellchecker.
List symspellpy dictionaries by order of cached vs non-cached.
symspellpy 6.3.8 is now the minimum version required and add support for showing
that information to the user.
Also add support for spellcheck libraries that are installed but without dicts.
This commit is contained in:
Youness Alaoui 2019-02-23 19:48:57 -05:00 committed by Curtis Gedak
parent e1edccc7d3
commit f9b181ff67
2 changed files with 48 additions and 29 deletions

View file

@ -14,8 +14,14 @@ try:
except ImportError:
pyspellchecker = None
SYMSPELLPY_MIN_VERSION = "6.3.8"
try:
import symspellpy
import distutils.version
if distutils.version.LooseVersion(symspellpy.__version__) < SYMSPELLPY_MIN_VERSION:
symspellpy = None
except ImportError:
symspellpy = None
@ -41,9 +47,9 @@ class Spellchecker:
@staticmethod
def supportedLibraries():
libs = []
libs = OrderedDict()
for impl in Spellchecker.implementations:
libs.append(impl.getLibraryName())
libs[impl.getLibraryName()] = impl.getLibraryRequirement()
return libs
@staticmethod
@ -58,7 +64,8 @@ class Spellchecker:
def availableDictionaries():
dictionaries = OrderedDict()
for impl in Spellchecker.implementations:
dictionaries[impl.getLibraryName()] = impl.availableDictionaries()
if impl.isInstalled():
dictionaries[impl.getLibraryName()] = impl.availableDictionaries()
return dictionaries
@staticmethod
@ -133,6 +140,10 @@ class BasicDictionary:
def getLibraryName():
raise NotImplemented
@staticmethod
def getLibraryRequirement():
return None
@staticmethod
def getLibraryURL():
raise NotImplemented
@ -247,8 +258,6 @@ class EnchantDictionary(BasicDictionary):
def getCustomDictionaryPath(self):
return os.path.join(self.getResourcesPath(), "{}.txt".format(self.name))
Spellchecker.implementations.append(EnchantDictionary)
class PySpellcheckerDictionary(BasicDictionary):
def __init__(self, name):
@ -309,9 +318,6 @@ class PySpellcheckerDictionary(BasicDictionary):
BasicDictionary.removeWord(self, word)
self._dict.word_frequency.remove(word.lower())
Spellchecker.registerImplementation(PySpellcheckerDictionary)
class SymSpellDictionary(BasicDictionary):
CUSTOM_COUNT = 1
DISTANCE = 2
@ -323,7 +329,9 @@ class SymSpellDictionary(BasicDictionary):
cachePath = self.getCachedDictionaryPath()
try:
self._dict.load_pickle(cachePath)
if not self._dict.load_pickle(cachePath, False):
raise Exception("Can't load cached dictionary. " +
"File might be corrupted or incompatible with installed symspellpy version")
except:
if pyspellchecker:
path = os.path.join(pyspellchecker.__path__[0], "resources", "{}.json.gz".format(self.name))
@ -332,17 +340,21 @@ class SymSpellDictionary(BasicDictionary):
data = json.loads(f.read())
for key in data:
self._dict.create_dictionary_entry(key, data[key])
self._dict.save_pickle(cachePath)
self._dict.save_pickle(cachePath, False)
for word in self._customDict:
self._dict.create_dictionary_entry(word, self.CUSTOM_COUNT)
def getCachedDictionaryPath(self):
return os.path.join(self.getResourcesPath(), "{}.sym.gz".format(self.name))
return os.path.join(self.getResourcesPath(), "{}.sym".format(self.name))
@staticmethod
def getLibraryName():
return "symspellpy"
@staticmethod
def getLibraryRequirement():
return ">= " + SYMSPELLPY_MIN_VERSION
@staticmethod
def getLibraryURL():
return "https://github.com/mammothb/symspellpy"
@ -354,11 +366,14 @@ class SymSpellDictionary(BasicDictionary):
@classmethod
def availableDictionaries(cls):
if SymSpellDictionary.isInstalled():
files = glob.glob(os.path.join(cls.getResourcesPath(), "*.sym.gz"))
dictionaries = set()
files = glob.glob(os.path.join(cls.getResourcesPath(), "*.sym"))
dictionaries = []
for file in files:
dictionaries.add(os.path.basename(file)[:-7])
return list(dictionaries.union(PySpellcheckerDictionary.availableDictionaries()))
dictionaries.append(os.path.basename(file)[:-4])
for sp_dict in PySpellcheckerDictionary.availableDictionaries():
if not sp_dict in dictionaries:
dictionaries.append(sp_dict)
return dictionaries
return []
@staticmethod
@ -402,15 +417,11 @@ class SymSpellDictionary(BasicDictionary):
def removeWord(self, word):
BasicDictionary.removeWord(self, word)
# Need to do this for now because library doesn't support removing a word
self._reloadDict()
# Since 6.3.8
self._dict.delete_dictionary_entry(word)
def _reloadDict(self):
self._dict = symspellpy.SymSpell(self.DISTANCE)
cachePath = self.getCachedDictionaryPath()
self._dict.load_pickle(cachePath)
for word in self._customDict:
self._dict.create_dictionary_entry(word, self.CUSTOM_COUNT)
# Register the implementations in order of priority
Spellchecker.implementations.append(EnchantDictionary)
Spellchecker.registerImplementation(SymSpellDictionary)
Spellchecker.registerImplementation(PySpellcheckerDictionary)

View file

@ -1255,8 +1255,8 @@ class MainWindow(QMainWindow, Ui_MainWindow):
else:
# No Spell check support
self.actSpellcheck.setVisible(False)
for lib in Spellchecker.supportedLibraries():
a = QAction(self.tr("Install {} to use spellcheck").format(lib), self)
for lib, requirement in Spellchecker.supportedLibraries().items():
a = QAction(self.tr("Install {}{} to use spellcheck").format(lib, requirement or ""), self)
a.setIcon(self.style().standardIcon(QStyle.SP_MessageBoxWarning))
# Need to bound the lib argument otherwise the lambda uses the same lib value across all calls
def gen_slot_cb(l):
@ -1275,11 +1275,12 @@ class MainWindow(QMainWindow, Ui_MainWindow):
return
self.menuDict.clear()
for lib, dicts in Spellchecker.availableDictionaries().items():
if len(dicts) > 1:
dictionaries = Spellchecker.availableDictionaries()
for lib, dicts in dictionaries.items():
if len(dicts) > 0:
a = QAction(lib, self)
else:
a = QAction(self.tr("{} is not installed").format(lib), self)
a = QAction(self.tr("{} has no installed dictionaries").format(lib), self)
a.setEnabled(False)
self.menuDict.addAction(a)
for i in dicts:
@ -1295,6 +1296,13 @@ class MainWindow(QMainWindow, Ui_MainWindow):
self.menuDict.addAction(a)
self.menuDict.addSeparator()
for lib, requirement in Spellchecker.supportedLibraries().items():
if lib not in dictionaries:
a = QAction(self.tr("{}{} is not installed").format(lib, requirement or ""), self)
a.setEnabled(False)
self.menuDict.addAction(a)
self.menuDict.addSeparator()
def setDictionary(self):
if not Spellchecker.isInstalled():
return