Files
esl-reader/lib/words.py
Christopher T. Johnson ea882a6de3 Checkpoint. Not working
2024-04-05 10:56:22 -04:00

448 lines
15 KiB
Python

import importlib
import pkgutil
import json
import re
from typing import Any, Dict, cast
from PyQt6.QtCore import (
Qt,
pyqtSlot,
)
from PyQt6.QtGui import (
QColor,
QFont,
QFontDatabase,
)
from PyQt6.QtNetwork import QNetworkAccessManager
from PyQt6.QtSql import QSqlQuery
from PyQt6.QtWidgets import QScrollArea
from lib import query_error
from lib.sounds import SoundOff
from lib.definition import Definition, Line, Fragment
import plugins
def find_plugins(ns_pkg):
return pkgutil.iter_modules(ns_pkg.__path__, ns_pkg.__name__ + '.')
discovered_plugins = {
# finder, name, ispkg
importlib.import_module(name).registration['source']: importlib.import_module(name) for _, name, _ in find_plugins(plugins)
}
API = "https://api.dictionaryapi.dev/api/v2/entries/en/{word}"
class Word:
"""All processing of a dictionary word."""
_words: dict[str, Any] = {}
_resources: Dict[str, Any] = {}
_nam = QNetworkAccessManager()
def __init__(self, word: str) -> None:
Word.set_resources()
#
# Have we already retrieved this word?
#
try:
self.current = json.loads(Word._words[word])
return
except KeyError:
pass
query = QSqlQuery()
query.prepare("SELECT * FROM words " "WHERE word = :word")
query.bindValue(":word", word)
if not query.exec():
query_error(query)
if query.next():
Word._words[word] = {
"word": word,
"source": query.value("source"),
"definition": json.loads(query.value("definition")),
}
self.current = Word._words[word]
return
#
# The code should look at our settings to see if we have an API
# key for MW to decide on the source to use.
#
source = "mw"
self._words[word] = discovered_plugins[source].fetch(word)
self.current = Word._words[word]
query.prepare(
"INSERT INTO words "
"(word, source, definition) "
"VALUES (:word, :source, :definition)"
)
query.bindValue(":word", self.current["word"])
query.bindValue(":source", self.current["source"])
query.bindValue(":definition", json.dumps(self.current["definition"]))
if not query.exec():
query_error(query)
return
@classmethod
def set_resources(cls) -> None:
if len(cls._resources.keys()) > 0:
return
#
# Colors we used
#
headerFont = QFontDatabase.font("OpenDyslexic", None, 10)
headerFont.setPixelSize(48)
labelFont = QFont(headerFont)
labelFont.setPixelSize(30)
boldFont = QFont(headerFont)
boldFont.setPixelSize(20)
textFont = QFont(boldFont)
italicFont = QFont(boldFont)
capsFont = QFont(boldFont)
smallCapsFont = QFont(boldFont)
headerFont.setWeight(QFont.Weight.Bold)
boldFont.setBold(True)
italicFont.setItalic(True)
capsFont.setCapitalization(QFont.Capitalization.AllUppercase)
smallCapsFont.setCapitalization(QFont.Capitalization.SmallCaps)
phonicFont = QFontDatabase.font("Gentium", None, 10)
phonicFont.setPixelSize(20)
cls._resources = {
"colors": {
"base": QColor(Qt.GlobalColor.white),
"link": QColor("#4a7d95"),
"subdued": QColor(Qt.GlobalColor.gray),
},
"fonts": {
"header": headerFont,
"label": labelFont,
"phonic": phonicFont,
"bold": boldFont,
"italic": italicFont,
"text": textFont,
"caps": capsFont,
"smallCaps": smallCapsFont,
},
}
@pyqtSlot()
def playSound(self) -> None:
url = discovered_plugins[self.current['source']].getFirstSound(self.current['definition'])
if url.isValid():
snd = SoundOff()
snd.playSound(url)
return
def getWord(self) -> str:
return cast(str, self.current["word"])
def get_html(self) -> str | None:
src = self.current['source']
try:
return discovered_plugins[src].getHtml(self.current)
except KeyError:
raise Exception(f"Unknown source: {src}")
def get_def(self) -> list[Line]:
if len(self._lines) > 0:
return self._lines
src = self.current['source']
try:
return discovered_plugins[src].getDef(self.current)
except KeyError:
raise Exception(f"Unknown source: {self.current['source']}")
def mw_def(self) -> list[Line]:
lines: list[Line] = []
# print(json.dumps(self.current,indent=2))
for entry in self.current["definition"]:
lines += self.mw_def_entry(entry)
self._lines = lines
return lines
def mw_seq(self, seq: list[Any]) -> list[Line]:
lines: list[Line] = []
outer = " "
inner = " "
for value in seq:
if value[0] == 'pseq':
continue
print(value[0])
print(value[1])
sense = value[1]
#
# The optional 'sn' field tells us what sort of labeling to do
#
sn = sense.get("sn", "")
sns = sn.split(" ")
if len(sns) == 2:
outer = sns[0]
inner = sns[1]
elif len(sns) == 1:
if inner == " ":
outer = sns[0]
else:
inner = sns[0]
try:
text = ", ".join(sense["sls"])
line = Line()
frag = Fragment(
f"{outer} {inner} ",
self._resources["fonts"]["bold"],
color=self._resources["colors"]["base"],
)
outer = " "
line.addFragment(frag)
frag = Fragment(
text,
self._resources["fonts"]["italic"],
color=self._resources["colors"]["base"],
)
frag.setLeft(30)
line.addFragment(frag)
except KeyError:
pass
try:
for dt in sense["dt"]:
if dt[0] == "text":
line = Line()
frag = Fragment(
f"{outer} {inner} ",
self._resources["fonts"]["bold"],
color=self._resources["colors"]["base"],
)
outer = " "
frag.setLeft(10)
line.addFragment(frag)
frag = Fragment(
dt[1],
self._resources["fonts"]["text"],
color=self._resources["colors"]["base"],
)
frag.setLeft(30)
line.addFragment(frag)
lines.append(line)
elif dt[0] == "vis":
for vis in dt[1]:
line = Line()
frag = Fragment(
f" ",
self._resources["fonts"]["bold"],
)
frag.setLeft(45)
line.addFragment(frag)
line.addFragment(
Fragment(
vis["t"],
self._resources["fonts"]["text"],
color=QColor("#aaa"),
)
)
lines.append(line)
elif dt[0] == "uns":
for uns in dt[1]:
for seg in uns:
if seg[0] == "text":
try:
line = lines.pop()
except IndexError:
line = Line()
frag = Fragment(
"\u27F6 " + seg[1],
self._resources["fonts"]["text"],
color=self._resources["colors"]["base"],
)
frag.setLeft(30)
line.addFragment(frag)
lines.append(line)
elif dt[0] == 'ca':
continue
else:
raise Exception(f"Unknown key {dt[0]} in {sense['dt']}")
except KeyError:
pass
return lines
def mw_def_entry(self, entry: dict[str, Any]) -> list[Line]:
#
# Easy reference to colors
#
base = self._resources["colors"]["base"]
blue = self._resources["colors"]["blue"]
lines: list[Line] = []
line = Line()
hw = re.sub(r"\*", "", entry["hwi"]["hw"])
frag = Fragment(hw, self._resources["fonts"]["header"], color=base)
line.addFragment(frag)
frag = Fragment(
" " + entry["fl"], self._resources["fonts"]["label"], color=blue
)
line.addFragment(frag)
lines.append(line)
if "vrs" in entry.keys():
line = Line()
space = ""
for vrs in entry["vrs"]:
frag = Fragment(
space + vrs["va"],
self._resources["fonts"]["label"],
color=base,
)
space = " "
line.addFragment(frag)
lines.append(line)
if "prs" in entry["hwi"]:
line = Line()
frag = Fragment(
entry["hwi"]["hw"] + " ",
self._resources["fonts"]["phonic"],
color=base,
)
line.addFragment(frag)
for prs in entry["hwi"]["prs"]:
audio = self.mw_sound_url(prs)
if audio is None:
audio = ""
frag = Fragment(
prs["mw"], self._resources["fonts"]["phonic"], color=blue
)
frag.setAudio(audio)
line.addFragment(frag)
lines.append(line)
if "ins" in entry.keys():
line = Line()
space = ""
for ins in entry["ins"]:
try:
frag = Fragment(
ins["il"], self._resources["fonts"]["text"], color=base
)
line.addFragment(frag)
space = " "
except KeyError:
pass
frag = Fragment(
space + ins["if"],
self._resources["fonts"]["bold"],
color=base,
)
line.addFragment(frag)
space = "; "
lines.append(line)
if "lbs" in entry.keys():
line = Line()
frag = Fragment(
"; ".join(entry["lbs"]),
self._resources["fonts"]["bold"],
color=base,
)
line.addFragment(frag)
lines.append(line)
for value in entry["def"]: # has multiple 'sseg' or 'vd' init
for k, v in value.items():
if k == "sseq": # has multiple 'senses'
for seq in v:
r = self.mw_seq(seq)
lines += r
elif k == "vd":
line = Line()
line.addFragment(
Fragment(
v, self._resources["fonts"]["italic"], color=blue
)
)
lines.append(line)
return lines
def mw_html(self) -> str:
#
# Create the header, base word and its label
#
word = self.current["hwi"]["hw"]
label = self.current["fl"]
html = f'<h1 class="def-word">{word} <span class="def-label">{label}</span></h1>\n'
#
# If there are variants, then add them in an unordered list.
# CSS will make it pretty
#
if "vrs" in self.current.keys():
html += "<ul class=\"def-vrs'>\n"
html += "<li>"
html += "</li>\n<li>".join(
[vrs["va"] for vrs in self.current["vrs"]]
)
html += "</li>\n</ul>\n"
#
# If there is a pronunciation section, create it
#
if "prs" in self.current["hwi"].keys():
tmp = []
for prs in self.current["hwi"]["prs"]:
url = self.mw_sound_url(prs)
how = prs["mw"]
if url:
tmp.append(f'<a href="{url}">\\{how}\\</a>')
else:
tmp.append(f"\\{how}\\")
html += '<span class="def-phonetic">'
html += '</span><span="def-phonetic">'.join(tmp)
html += "</span>\n"
#
# If there are inflections, create a header for that.
#
if "ins" in self.current.keys():
html += '<h2 class="def-word">'
html += ", ".join([ins["if"] for ins in self.current["ins"]])
html += "</h2>\n"
#
# Start creating the definition section
#
html += "<ul class='def-outer'>\n"
for meaning in self.current["def"]:
html += f"<li>{meaning['vd']}\n"
html += '<ul class="def-inner">\n'
label = ""
for sseq in meaning["sseq"]:
for sense in sseq:
label = sense[1]["sn"]
sls = ""
if "sls" in sense[1].keys():
sls = ", ".join(sense[1]["sls"])
sls = f'<span class="def-sls">{sls}</span> '
for dt in sense[1]["dt"]:
if dt[0] == "text":
html += f'<li class="def-text"><span class="def-sn">{label}</span>{sls}{dt[1]}</li>\n'
elif dt[0] == "vis":
for vis in dt[1]:
html += (
f"<li class=\"def-vis\">{vis['t']}</li>\n"
)
else:
print(f"Do something with {dt[0]}")
html += "</ul>\n"
html += "</ul>\n"
return html
def apidictionary_html(self) -> str:
html = ""
return html
class DefinitionArea(QScrollArea):
def __init__(self, w: Word, *args: Any, **kwargs: Any) -> None:
super(DefinitionArea, self).__init__(*args, *kwargs)
d = Definition(w)
self.setWidget(d)
self.setWidgetResizable(True)
self.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOn)
return