Fix handling of empty spellcheck language string (#1098)

This commit is contained in:
Veronica Berglyd Olsen
2022-08-17 22:34:45 +02:00
committed by GitHub
6 changed files with 110 additions and 33 deletions
+1 -1
View File
@@ -25,7 +25,7 @@ jobs:
- name: Install Packages (apt)
run: |
sudo apt update
sudo apt install libenchant-dev qttools5-dev-tools
sudo apt install libenchant-dev qttools5-dev-tools aspell-en
- name: Checkout Source
uses: actions/checkout@v2
- name: Install Dependencies (pip)
+1 -1
View File
@@ -360,7 +360,7 @@ class Config:
# Check the availability of optional packages
self._checkOptionalPackages()
if self.spellLanguage is None:
if not self.spellLanguage:
self.spellLanguage = "en"
# Look for a PDF version of the manual
+30 -14
View File
@@ -26,6 +26,8 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
import os
import logging
from collections import namedtuple
from novelwriter.error import logException
logger = logging.getLogger(__name__)
@@ -46,36 +48,47 @@ class NWSpellEnchant():
return
##
# Getters and Setters
# Properties
##
@property
def spellLanguage(self):
return self._spellLanguage
##
# Setters
##
def setLanguage(self, theLang, projectDict=None):
"""Load a dictionary for the language specified in the config.
If that fails, we load a mock dictionary so that lookups don't
crash.
crash. Note that enchant will allow loading an empty string as
a tag, but this will fail later on. See issue #1096.
"""
self._theBroker = None
self._theDict = None
self._spellLanguage = None
try:
import enchant
if self._theBroker is not None:
logger.debug("Deleting old pyenchant broker")
del self._theBroker
self._theBroker = enchant.Broker()
self._theDict = self._theBroker.request_dict(theLang)
self._spellLanguage = theLang
logger.debug("Enchant spell checking for language '%s' loaded", theLang)
if theLang and enchant.dict_exists(theLang):
self._theBroker = enchant.Broker()
self._theDict = self._theBroker.request_dict(theLang)
self._spellLanguage = theLang
logger.debug("Enchant spell checking for language '%s' loaded", theLang)
else:
logger.warning("Enchant found no dictionary for language '%s'", theLang)
except Exception:
logger.error("Failed to load enchant spell checking for language '%s'", theLang)
self._theDict = FakeEnchant()
self._spellLanguage = None
self._readProjectDictionary(projectDict)
for pWord in self._projDict:
self._theDict.add_to_session(pWord)
if self._theDict is None:
self._theDict = FakeEnchant()
else:
self._readProjectDictionary(projectDict)
for pWord in self._projDict:
self._theDict.add_to_session(pWord)
return
@@ -189,6 +202,9 @@ class FakeEnchant:
"""Fallback for when Enchant is selected, but not installed.
"""
def __init__(self):
self.tag = ""
self.provider = namedtuple("provider", "name")
self.provider.name = ""
return
def check(self, theWord):
+1 -1
View File
@@ -718,7 +718,7 @@ class GuiDocEditor(QTextEdit):
), nwAlert.INFO)
theMode = False
if self.spEnchant.spellLanguage() is None:
if self.spEnchant.spellLanguage is None:
theMode = False
self._spellCheck = theMode
+76 -15
View File
@@ -26,29 +26,57 @@ import pytest
from mock import causeOSError
from tools import readFile, writeFile
from novelwriter.core.spellcheck import NWSpellEnchant
from novelwriter.core.spellcheck import FakeEnchant, NWSpellEnchant
@pytest.mark.core
def testCoreSpell_Enchant(monkeypatch, tmpDir):
"""Test the pyenchant spell checker
def testCoreSpell_FakeEnchant(monkeypatch):
"""Test the FakeEnchant spell checker fallback.
"""
wList = os.path.join(tmpDir, "wordlist.txt")
writeFile(wList, "a_word\nb_word\nc_word\n")
# Block the enchant package (and trigger the default class)
# Make package import fail
with monkeypatch.context() as mp:
mp.setitem(sys.modules, "enchant", None)
spChk = NWSpellEnchant()
spChk.setLanguage("en_US", "")
assert isinstance(spChk._theDict, FakeEnchant)
spChk.setLanguage("en", wList)
assert spChk.setLanguage("", "") is None
assert spChk.checkWord("") is True
assert spChk.suggestWords("") == []
# Request a non-existent dictionary
spChk = NWSpellEnchant()
spChk.setLanguage("whatchamajig", "")
assert isinstance(spChk._theDict, FakeEnchant)
# Request an emety language string
# See issue https://github.com/vkbo/novelWriter/issues/1096
spChk = NWSpellEnchant()
spChk.setLanguage("", "")
assert isinstance(spChk._theDict, FakeEnchant)
# FakeEnchant should handle requests
fkChk = FakeEnchant()
assert fkChk.tag == ""
assert fkChk.provider.name == ""
assert fkChk.check("whatchamajig") is True
assert fkChk.suggest("whatchamajig") == []
assert fkChk.add_to_session("whatchamajig") is None
# END Test testCoreSpell_FakeEnchant
@pytest.mark.core
def testCoreSpell_Enchant(monkeypatch, fncDir):
"""Test the pyenchant spell checker.
"""
wList = os.path.join(fncDir, "wordlist.txt")
writeFile(wList, "a_word\nb_word\nc_word\n")
# Break the enchant package, and check error handling
with monkeypatch.context() as mp:
mp.setitem(sys.modules, "enchant", None)
spChk = NWSpellEnchant()
assert spChk.listDictionaries() == []
assert spChk.describeDict() == ("", "")
# Break the enchant package, and check error handling
# Set the dict to None, and check dictionary call error handling
spChk = NWSpellEnchant()
spChk.theDict = None
assert spChk.checkWord("word") is True
@@ -57,8 +85,9 @@ def testCoreSpell_Enchant(monkeypatch, tmpDir):
# Load the proper enchant package (twice)
spChk = NWSpellEnchant()
spChk.setLanguage("en", wList)
spChk.setLanguage("en", wList)
spChk.setLanguage("en_US", wList)
spChk.setLanguage("en_US", wList)
assert spChk.spellLanguage == "en_US"
# Add a word to the user's dictionary
assert spChk._readProjectDictionary("stuff") is False
@@ -98,7 +127,39 @@ def testCoreSpell_Enchant(monkeypatch, tmpDir):
assert len(dList) > 0
aTag, aName = spChk.describeDict()
assert aTag == "en"
assert aTag == "en_US"
assert aName != ""
# END Test testCoreSpell_Enchant
@pytest.mark.core
def testCoreSpell_SessionWords(fncDir):
"""Test the handling of the custom word list in the spell checker.
New project sessions should not inherit the project word list from
other sessions, so this test checks that they don't bleed through.
"""
wList1 = os.path.join(fncDir, "wordlist1.txt")
wList2 = os.path.join(fncDir, "wordlist2.txt")
writeFile(wList1, "a_word\nb_word\nc_word\n")
writeFile(wList2, "d_word\ne_word\nf_word\n")
spChk = NWSpellEnchant()
spChk.setLanguage("en_US", wList1)
assert spChk.checkWord("a_word") is True
assert spChk.checkWord("b_word") is True
assert spChk.checkWord("c_word") is True
assert spChk.checkWord("d_word") is False
assert spChk.checkWord("e_word") is False
assert spChk.checkWord("f_word") is False
spChk.setLanguage("en_US", wList2)
assert spChk.checkWord("a_word") is False
assert spChk.checkWord("b_word") is False
assert spChk.checkWord("c_word") is False
assert spChk.checkWord("d_word") is True
assert spChk.checkWord("e_word") is True
assert spChk.checkWord("f_word") is True
# END Test testCoreSpell_SessionWords
+1 -1
View File
@@ -439,7 +439,7 @@ def testGuiMain_Editing(qtbot, monkeypatch, nwGUI, fncProj, refDir, outDir):
testFile = os.path.join(outDir, "guiEditor_Main_Final_nwProject.nwx")
compFile = os.path.join(refDir, "guiEditor_Main_Final_nwProject.nwx")
copyfile(projFile, testFile)
assert cmpFiles(testFile, compFile, [2, 6, 7, 8])
assert cmpFiles(testFile, compFile, [2, 6, 7, 8, 13])
projFile = os.path.join(fncProj, "content", "031b4af5197ec.nwd")
testFile = os.path.join(outDir, "guiEditor_Main_Final_031b4af5197ec.nwd")