import importlib import pkgutil import json import re from typing import Any, TypedDict, cast from PyQt6.QtCore import ( QUrl, Qt, pyqtSlot, ) from PyQt6.QtGui import ( QColor, ) from PyQt6.QtSql import QSqlQuery from PyQt6.QtWidgets import QScrollArea from lib.utils import query_error, Resources 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 WordType(TypedDict): word: str source: str definition: str class Word: """All processing of a dictionary word.""" _words: dict[str, WordType] = {} def __init__(self, word: str) -> None: # # Have we already retrieved this word? # try: self.current = 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 @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]: src = self.current['source'] try: lines = discovered_plugins[src].getDef(self.current["definition"]) return lines except KeyError: raise Exception(f"Unknown source: {self.current['source']}") def mw_seq(self, seq: list[Any]) -> list[Line]: r=Resources() 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} ", r.boldFont, color=r.baseColor ) outer = " " line.addFragment(frag) frag = Fragment( text, r.italicFont, color=r.baseColor ) 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} ", r.boldFont, color=r.baseColor ) outer = " " frag.setLeft(10) line.addFragment(frag) frag = Fragment( dt[1], r.textFont, color=r.baseColor ) frag.setLeft(30) line.addFragment(frag) lines.append(line) elif dt[0] == "vis": for vis in dt[1]: line = Line() frag = Fragment( f" ", r.boldFont ) frag.setLeft(45) line.addFragment(frag) line.addFragment( Fragment( vis["t"], r.textFont, 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], r.textFont, color=r.baseColor ) 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]: r = Resources() # # Easy reference to colors # lines: list[Line] = [] line = Line() hw = re.sub(r"\*", "", entry["hwi"]["hw"]) frag = Fragment(hw, r.headerFont, color=r.baseColor) line.addFragment(frag) frag = Fragment( " " + entry["fl"], r.labelFont, color=r.linkColor ) line.addFragment(frag) lines.append(line) if "vrs" in entry.keys(): line = Line() space = "" for vrs in entry["vrs"]: frag = Fragment( space + vrs["va"], r.labelFont, color=r.baseColor ) space = " " line.addFragment(frag) lines.append(line) if "prs" in entry["hwi"]: line = Line() frag = Fragment( entry["hwi"]["hw"] + " ", r.phonicFont, color=r.baseColor, ) line.addFragment(frag) for prs in entry["hwi"]["prs"]: audio = None if audio is None: audio = "" frag = Fragment( prs["mw"], r.phonicFont, color=r.linkColor ) 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"], r.textFont, color=r.baseColor ) line.addFragment(frag) space = " " except KeyError: pass frag = Fragment( space + ins["if"], r.boldFont, color=r.baseColor ) line.addFragment(frag) space = "; " lines.append(line) if "lbs" in entry.keys(): line = Line() frag = Fragment( "; ".join(entry["lbs"]), r.boldFont, color=r.baseColor ) 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: rr = self.mw_seq(seq) lines += rr elif k == "vd": line = Line() line.addFragment( Fragment( v, r.italicFont, color=r.linkColor ) ) lines.append(line) return lines def mw_html(self) -> str: # # Create the header, base word and its label # word = self.current['definition']["hwi"]["hw"] label = self.current["fl"] html = f'