From 51b1121176b5c69c4d6562043e9d9601661927d1 Mon Sep 17 00:00:00 2001 From: "Christopher T. Johnson" Date: Tue, 16 Apr 2024 11:50:26 -0400 Subject: [PATCH] Lint --- lib/__init__.py | 2 +- lib/definition.py | 110 ++++---- lib/sounds.py | 8 +- lib/utils.py | 3 +- lib/words.py | 54 ++-- plugins/merriam-webster.py | 519 +++++++++++++++++++++++-------------- 6 files changed, 422 insertions(+), 274 deletions(-) diff --git a/lib/__init__.py b/lib/__init__.py index d0e6412..79f826a 100644 --- a/lib/__init__.py +++ b/lib/__init__.py @@ -1,8 +1,8 @@ # pyright: ignore from .utils import query_error # isort: skip from .books import Book +from .definition import Definition, Fragment, Line from .person import PersonDialog from .read import ReadDialog from .session import SessionDialog from .words import DefinitionArea, Word -from .definition import Fragment, Line, Definition diff --git a/lib/definition.py b/lib/definition.py index 2ef474f..59e495e 100644 --- a/lib/definition.py +++ b/lib/definition.py @@ -1,27 +1,40 @@ import re -from typing import Any, Optional, Self, cast, overload -import re -from PyQt6.QtCore import QMargins, QPoint, QRect, QSize, QUrl, Qt, pyqtSignal -from PyQt6.QtGui import QColor, QFont, QFontMetrics, QMouseEvent, QPaintEvent, QPainter, QResizeEvent, QTextOption, QTransform, QBrush +from typing import Any, Callable, Optional, Self, cast, overload + +from PyQt6.QtCore import QMargins, QPoint, QRect, QSize, Qt, QUrl, pyqtSignal +from PyQt6.QtGui import ( + QBrush, + QColor, + QFont, + QFontMetrics, + QMouseEvent, + QPainter, + QPaintEvent, + QResizeEvent, + QTextOption, + QTransform, +) from PyQt6.QtWidgets import QWidget + class Fragment: """A fragment of text to be displayed""" + _indentAmount = 35 def __init__( - self, - which: str|Self, - font: QFont|None = None, - audio: str = "", - color: Optional[QColor] = None, - asis: bool = False, + self, + which: str | Self, + font: QFont | None = None, + audio: str = "", + color: Optional[QColor] = None, + asis: bool = False, ) -> None: if isinstance(which, Fragment): - for k,v in which.__dict__.items(): + for k, v in which.__dict__.items(): self.__dict__[k] = v return - self._text:str = which + self._text: str = which if font is None: raise TypeError("Missing required parameter 'font'") self._font = font @@ -63,13 +76,14 @@ class Fragment: return f"({self._position.x()}, {self._position.y()}): {self._text}" @overload - def paintEvent(self, widthSrc:int) -> QSize: + def paintEvent(self, widthSrc: int) -> QSize: ... + @overload def paintEvent(self, widthSrc: QPainter) -> int: ... - def paintEvent(self, widthSrc) -> int|QSize: + def paintEvent(self, widthSrc: QPainter | int) -> int | QSize: if isinstance(widthSrc, QPainter): viewportWidth = widthSrc.viewport().width() painter = widthSrc @@ -77,28 +91,21 @@ class Fragment: viewportWidth = widthSrc painter = None fm = QFontMetrics(self._font) - top = ( - self._position.y() - + fm.descent() - - fm.height() - ) + top = self._position.y() + fm.descent() - fm.height() left = self._position.x() width = viewportWidth - left height = 2000 rect = QRect(left, top, width, height) indent = self._indent * self._indentAmount - flags = ( - Qt.AlignmentFlag.AlignLeft - | Qt.AlignmentFlag.AlignBaseline - ) + flags = Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignBaseline boundingNoWrap = fm.boundingRect( - rect, flags|Qt.TextFlag.TextSingleLine, self._text + rect, flags | Qt.TextFlag.TextSingleLine, self._text ) bounding = fm.boundingRect( - rect, flags|Qt.TextFlag.TextWordWrap, self._text + rect, flags | Qt.TextFlag.TextWordWrap, self._text ) text = self._text - remainingText = '' + remainingText = "" if boundingNoWrap.height() < bounding.height(): # # This is not optimal, but it is only a few iterations @@ -107,39 +114,29 @@ class Fragment: char = 0 pos = rect.x() while pos < rect.right(): - if text[char] == ' ': + if text[char] == " ": lastSpace = char - pos += fm.horizontalAdvance( - text[char] - ) + pos += fm.horizontalAdvance(text[char]) char += 1 if lastSpace > 0: - remainingText = text[lastSpace+1:] + remainingText = text[lastSpace + 1 :] text = text[:lastSpace] size = boundingNoWrap.size() boundingNoWrap = fm.boundingRect( - rect, flags|Qt.TextFlag.TextSingleLine, text + rect, flags | Qt.TextFlag.TextSingleLine, text ) rect.setSize(boundingNoWrap.size()) - - if remainingText != '': + if remainingText != "": top += size.height() - remainingRect = QRect( - indent, top, - viewportWidth - indent, height - ) + remainingRect = QRect(indent, top, viewportWidth - indent, height) boundingRemaingRect = fm.boundingRect( remainingRect, flags | Qt.TextFlag.TextWordWrap, remainingText ) - size = size.grownBy( - QMargins( - 0,0,0, boundingRemaingRect.height() - ) - ) + size = size.grownBy(QMargins(0, 0, 0, boundingRemaingRect.height())) remainingRect.setSize(boundingRemaingRect.size()) size = size.grownBy(self._margin) size = size.grownBy(self._border) @@ -163,12 +160,14 @@ class Fragment: brush.setColor(self._background) brush.setStyle(Qt.BrushStyle.SolidPattern) painter.setBrush(brush) - painter.fillRect(rect,brush) + painter.fillRect(rect, brush) painter.drawText(rect, flags, text) if remainingText: if self._background.isValid(): painter.fillRect(remainingRect, brush) - painter.drawText(remainingRect, flags|Qt.TextFlag.TextWordWrap, remainingText) + painter.drawText( + remainingRect, flags | Qt.TextFlag.TextWordWrap, remainingText + ) painter.restore() return size.height() @@ -310,6 +309,7 @@ class Fragment: def setBackground(self, color: QColor) -> None: self._background = color return + def setIndent(self, indent: int) -> None: self._indent = indent return @@ -365,8 +365,10 @@ class Fragment: def pixelIndent(self) -> int: return self._indent * self._indentAmount + class Line: parseText = None + def __init__(self) -> None: self._maxHeight = -1 self._baseLine = -1 @@ -379,11 +381,12 @@ class Line: "|".join([x.text() for x in self._fragments]) + f"|{self._maxHeight}" ) + @classmethod - def setParseText(cls, call) -> None: + def setParseText(cls, call: Callable) -> None: cls.parseText = call return - + def paintEvent(self, painter: QPainter) -> int: # # we do not have an event field because we are not a true widget @@ -395,12 +398,16 @@ class Line: lineSpacing = ls return lineSpacing - - def addFragment(self, frags: Fragment|list[Fragment],) -> None: + def addFragment( + self, + frags: Fragment | list[Fragment], + ) -> None: SPEAKER = "\U0001F508" if not isinstance(frags, list): - frags = [frags, ] + frags = [ + frags, + ] for frag in frags: if frag.audio().isValid(): frag.setText(frag.text() + " " + SPEAKER) @@ -485,6 +492,7 @@ class Line: def getLineSpacing(self) -> int: return self._leading + self._maxHeight + class Definition(QWidget): pronounce = pyqtSignal(str) @@ -499,7 +507,7 @@ class Definition(QWidget): def setWord(self, word: Any) -> None: self._word = word - lines:list[Line] = word.get_def() + lines: list[Line] = word.get_def() assert lines is not None self._lines = lines self._buttons: list[Fragment] = [] diff --git a/lib/sounds.py b/lib/sounds.py index 90a6713..f19c6c0 100644 --- a/lib/sounds.py +++ b/lib/sounds.py @@ -101,7 +101,7 @@ class SoundOff(QObject): @pyqtSlot(QMediaPlayer.MediaStatus) def mediaStatus(self, status: QMediaPlayer.MediaStatus) -> None: - #print(f"mediaStatus: {status}") + # print(f"mediaStatus: {status}") if status == QMediaPlayer.MediaStatus.LoadedMedia: player: Optional[QMediaPlayer] = cast(QMediaPlayer, self.sender()) assert player is not None @@ -110,7 +110,7 @@ class SoundOff(QObject): @pyqtSlot(QMediaPlayer.PlaybackState) def playbackState(self, state: QMediaPlayer.PlaybackState) -> None: - #print(f"playbackState: {state}") + # print(f"playbackState: {state}") return # @@ -164,7 +164,7 @@ class SoundOff(QObject): continue self._storage[player] = QByteArray(storage) crypto.addData(self._storage[player]) - #print(player, crypto.result().toHex()) + # print(player, crypto.result().toHex()) crypto.reset() self._buffer[player] = QBuffer(self._storage[player]) url = reply.request().url() @@ -172,5 +172,5 @@ class SoundOff(QObject): player.setPosition(0) if player.mediaStatus() == QMediaPlayer.MediaStatus.LoadedMedia: player.play() - #print("play") + # print("play") return diff --git a/lib/utils.py b/lib/utils.py index 5e1ac7b..4e492b1 100644 --- a/lib/utils.py +++ b/lib/utils.py @@ -22,6 +22,7 @@ def query_error(query: QSqlQuery) -> NoReturn: ) raise Exception(translate("MainWindow", "SQL Error")) + class Resources: _instance = None nam = QNetworkAccessManager() @@ -39,7 +40,7 @@ class Resources: subduedColor: QColor subduedBackground: QColor - + def __new__(cls: type[Self]) -> Self: if cls._instance: return cls._instance diff --git a/lib/words.py b/lib/words.py index 0ecc5ef..c97012d 100644 --- a/lib/words.py +++ b/lib/words.py @@ -1,40 +1,46 @@ import importlib -import pkgutil import json -from typing import Any, TypedDict, cast +import pkgutil +from types import ModuleType +from typing import Any, Iterable, TypedDict, cast -from PyQt6.QtCore import ( - Qt, - pyqtSlot, -) +from PyQt6.QtCore import Qt, pyqtSlot from PyQt6.QtSql import QSqlQuery from PyQt6.QtWidgets import QScrollArea - -from lib.utils import query_error -from lib.sounds import SoundOff -from lib.definition import Definition, Line +from trycast import trycast import plugins -def find_plugins(ns_pkg): - return pkgutil.iter_modules(ns_pkg.__path__, ns_pkg.__name__ + '.') +from lib.definition import Definition, Line +from lib.sounds import SoundOff +from lib.utils import query_error + + +def find_plugins(ns_pkg: ModuleType) -> Iterable[pkgutil.ModuleInfo]: + 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) + 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? @@ -79,30 +85,38 @@ class Word: @pyqtSlot() def playSound(self) -> None: - url = discovered_plugins[self.current['source']].getFirstSound(self.current['definition']) + url = discovered_plugins[self.current["source"]].getFirstSound( + self.current["definition"] + ) if url.isValid(): snd = SoundOff() snd.playSound(url) return + def playPRS(self) -> None: + return + def getWord(self) -> str: - return cast(str, self.current["word"]) + return self.current["word"] def get_html(self) -> str | None: - src = self.current['source'] + src = self.current["source"] try: - return discovered_plugins[src].getHtml(self.current) + return cast(str, discovered_plugins[src].getHtml(self.current)) except KeyError: raise Exception(f"Unknown source: {src}") def get_def(self) -> list[Line]: - src = self.current['source'] + src = self.current["source"] try: lines = discovered_plugins[src].getDef(self.current["definition"]) + lines = trycast(list[Line], lines) + assert lines is not None return lines except KeyError: raise Exception(f"Unknown source: {self.current['source']}") + class DefinitionArea(QScrollArea): def __init__(self, w: Word, *args: Any, **kwargs: Any) -> None: super(DefinitionArea, self).__init__(*args, *kwargs) diff --git a/plugins/merriam-webster.py b/plugins/merriam-webster.py index 36e7e81..610ce31 100644 --- a/plugins/merriam-webster.py +++ b/plugins/merriam-webster.py @@ -1,23 +1,25 @@ -from PyQt6.QtGui import QColor, QFont -from trycast import trycast import json import re from typing import Any, Literal, NotRequired, TypedDict, cast -from PyQt6.QtCore import QEventLoop, QUrl, Qt +from PyQt6.QtCore import QEventLoop, Qt, QUrl +from PyQt6.QtGui import QColor, QFont from PyQt6.QtNetwork import QNetworkRequest +from trycast import trycast + +from lib.definition import Fragment, Line from lib.utils import Resources -from lib.definition import Line, Fragment registration = { - 'source': 'mw', - 'name': 'Merriam-Webster', - 'language': 'en-us', + "source": "mw", + "name": "Merriam-Webster", + "language": "en-us", } API = "https://www.dictionaryapi.com/api/v3/references/collegiate/json/{word}?key={key}" key = "51d9df34-ee13-489e-8656-478c215e846c" + class Meta(TypedDict): id: str uuid: str @@ -27,11 +29,13 @@ class Meta(TypedDict): stems: list[str] offensive: bool + class Sound(TypedDict): audio: str ref: str stat: str + class Pronunciation(TypedDict): mw: str l: NotRequired[str] @@ -39,41 +43,52 @@ class Pronunciation(TypedDict): pun: NotRequired[str] sound: NotRequired[Sound] + class SubSource(TypedDict): source: NotRequired[str] aqdate: NotRequired[str] + class AttributionOfQuote(TypedDict): auth: NotRequired[str] source: NotRequired[str] aqdate: NotRequired[str] subsource: NotRequired[SubSource] + class VerbalIllustration(TypedDict): t: str aq: NotRequired[AttributionOfQuote] + class HeadWordInformation(TypedDict): hw: str prs: NotRequired[list[Pronunciation]] + class AlternanteHeadword(TypedDict): hw: str psl: NotRequired[str] + class Variant(TypedDict): va: str vl: NotRequired[str] prs: NotRequired[list[Pronunciation]] spl: NotRequired[str] -Inflection = TypedDict('Inflection', { - 'if': NotRequired[str], - 'ifc': NotRequired[str], - 'il': NotRequired[str], - 'prs': NotRequired[list[Pronunciation]], - 'spl': NotRequired[str] - }) + +Inflection = TypedDict( + "Inflection", + { + "if": NotRequired[str], + "ifc": NotRequired[str], + "il": NotRequired[str], + "prs": NotRequired[list[Pronunciation]], + "spl": NotRequired[str], + }, +) + class CrossReferenceTarget(TypedDict): cxl: str @@ -81,14 +96,17 @@ class CrossReferenceTarget(TypedDict): cxt: str cxn: NotRequired[str] + class CognateCrossRef(TypedDict): cxl: str cxtis: list[CrossReferenceTarget] + class Pair(TypedDict): objType: str obj: Any + class DividedSense(TypedDict): sd: str dt: list[list[Pair]] @@ -100,6 +118,7 @@ class DividedSense(TypedDict): sls: NotRequired[list[str]] vrs: NotRequired[list[Variant]] + class Sense(TypedDict): dt: list[list[Pair]] et: NotRequired[list[Pair]] @@ -112,55 +131,89 @@ class Sense(TypedDict): sn: NotRequired[str] vrs: NotRequired[list[Variant]] -class TruncatedSense(Sense): pass + +class TruncatedSense(Sense): + pass + class BindingSubstitutePair(TypedDict): - objType: Literal['bs'] + objType: Literal["bs"] obj: Sense + class SensePair(TypedDict): - objType: Literal['sense'] + objType: Literal["sense"] obj: Sense + class DefinitionSection(TypedDict): vd: NotRequired[str] sls: NotRequired[list[str]] - sseq: Any # list[list[Pair]] - -Definition =TypedDict('Definition', { - 'meta': Meta, - 'hom': NotRequired[int], - 'hwi': HeadWordInformation, - 'ahws': NotRequired[list[AlternanteHeadword]], - 'vrs': NotRequired[list[Variant]], - 'fl': str, - 'lbs': NotRequired[list[str]], - 'sls': NotRequired[list[str]], - 'ins': NotRequired[list[Inflection]], - 'cxs': NotRequired[list[CognateCrossRef]], - 'def': list[DefinitionSection], -}) + sseq: Any # list[list[Pair]] + + +Definition = TypedDict( + "Definition", + { + "ahws": NotRequired[list[AlternanteHeadword]], + "cxs": NotRequired[list[CognateCrossRef]], + "date": NotRequired[str], + "def": list[DefinitionSection], + "dros": NotRequired[Any], + "et": NotRequired[list[Pair]], + "fl": str, + "hom": NotRequired[int], + "hwi": HeadWordInformation, + "ins": NotRequired[list[Inflection]], + "lbs": NotRequired[list[str]], + "meta": Meta, + "shortdef": NotRequired[list[str]], + "sls": NotRequired[list[str]], + "syns": NotRequired[Any], + "uros": NotRequired[Any], + "vrs": NotRequired[list[Variant]], + }, +) + def make_pairs(src: list[Any]) -> list[Pair]: - result:list[Pair] = [] - iters = [iter(src)]*2 + result: list[Pair] = [] + iters = [iter(src)] * 2 for entry in zip(*iters): - pair0 = { 'objType': entry[0], - 'obj': entry[1], - } - if isinstance(pair0['obj'], list): - result.append(cast(Pair,pair0)) + pair0 = { + "objType": entry[0], + "obj": entry[1], + } + if isinstance(pair0["obj"], list): + result.append(cast(Pair, pair0)) continue pair1 = trycast(Pair, pair0) if pair1 is None: - print(pair0['objType'], type(pair0['obj']), - json.dumps(pair0['obj'],indent=2) - ) + print( + pair0["objType"], + type(pair0["obj"]), + json.dumps(pair0["obj"], indent=2), + ) assert pair1 is not None result.append(pair1) return result -Elements = [ 'dt', 'sen', 'bs', 'pseq', 'snot', 't', 'text', 'vis', 'sens', 'uns', 'sense' ] + +Elements = [ + "dt", + "sen", + "bs", + "pseq", + "snot", + "t", + "text", + "vis", + "sens", + "uns", + "sense", +] + + def restructure(obj: Any) -> Any: if isinstance(obj, list): if len(obj) == 0: @@ -169,16 +222,16 @@ def restructure(obj: Any) -> Any: pairs = make_pairs(obj) result = [] for pair in pairs: - if isinstance(pair['obj'], list): + if isinstance(pair["obj"], list): r2 = [] - for item in pair['obj']: + for item in pair["obj"]: r2.append(restructure(item)) - pair['obj'] = r2 - elif isinstance(pair['obj'], dict): + pair["obj"] = r2 + elif isinstance(pair["obj"], dict): r2 = {} - for k,v in pair['obj'].items(): + for k, v in pair["obj"].items(): r2[k] = restructure(v) - pair['obj'] = r2 + pair["obj"] = r2 result.append(pair) return result result = [] @@ -188,18 +241,20 @@ def restructure(obj: Any) -> Any: elif isinstance(obj, dict): obj2 = cast(dict, obj) result = {} - for k,v in obj2.items(): + for k, v in obj2.items(): result[k] = restructure(v) return result else: return obj + class WordType(TypedDict): word: str source: str definition: Any -def fetch(word:str) -> WordType: + +def fetch(word: str) -> WordType: request = QNetworkRequest() url = QUrl(API.format(word=word, key=key)) request.setUrl(url) @@ -210,17 +265,18 @@ def fetch(word:str) -> WordType: reply.finished.connect(loop.quit) loop.exec() content = reply.readAll() - data = json.loads(content.data().decode('utf-8')) + data = json.loads(content.data().decode("utf-8")) return { - 'word': word, - 'source': 'mw', - 'definition': data, + "word": word, + "source": "mw", + "definition": data, } -def soundUrl(sound:Sound, fmt='ogg') -> QUrl: + +def soundUrl(sound: Sound, fmt="ogg") -> QUrl: """Create a URL from a PRS structure.""" base = f"https://media.merriam-webster.com/audio/prons/en/us/{fmt}" - audio = sound['audio'] + audio = sound["audio"] m = re.match(r"(bix|gg|[a-zA-Z])", audio) if m: url = base + f"/{m.group(1)}/" @@ -229,21 +285,23 @@ def soundUrl(sound:Sound, fmt='ogg') -> QUrl: url += audio + f".{fmt}" return QUrl(url) + def getFirstSound(definition: Any) -> QUrl: # ahws, cats, dros, hwi, ins, ri, sdsense, sen, sense, uros, vrs for entry in definition: for v in entry.values(): - hwi = v # trycast + hwi = v # trycast if hwi is None: continue - if 'prs' in hwi: - for pr in hwi['prs']: - if 'sound' in pr: - url = soundUrl(pr['sound']) + if "prs" in hwi: + for pr in hwi["prs"]: + if "sound" in pr: + url = soundUrl(pr["sound"]) if url.isValid(): return url return QUrl() + def do_prs(prs: list[Pronunciation] | None) -> list[Fragment]: assert prs is not None r = Resources() @@ -253,46 +311,49 @@ def do_prs(prs: list[Pronunciation] | None) -> list[Fragment]: subduedColor = r.subduedColor for pr in prs: - if 'pun' in pr: - pun = pr['pun'] + if "pun" in pr: + pun = pr["pun"] else: - pun = ' ' - if 'l' in pr: + pun = " " + if "l" in pr: frags.append( - Fragment(pr['l'] + pun, r.italicFont, color=subduedColor) + Fragment(pr["l"] + pun, r.italicFont, color=subduedColor) ) - frag = Fragment(pr['mw'], font, color=subduedColor) - if 'sound' in pr: - frag.setAudio(soundUrl(pr['sound'])) + frag = Fragment(pr["mw"], font, color=subduedColor) + if "sound" in pr: + frag.setAudio(soundUrl(pr["sound"])) frag.setColor(linkColor) frags.append(frag) - frags.append(Fragment(' ', r.phonicFont)) - if 'l2' in pr: - frags.append( - Fragment(pun + pr['l2'], font, color=subduedColor) - ) + frags.append(Fragment(" ", r.phonicFont)) + if "l2" in pr: + frags.append(Fragment(pun + pr["l2"], font, color=subduedColor)) return frags -def do_aq(aq: AttributionOfQuote|None) -> list[Line]: + +def do_aq(aq: AttributionOfQuote | None) -> list[Line]: assert aq is not None return [] -def do_vis(vis: list[VerbalIllustration]|None,indent=0) -> list[Line]: + +def do_vis(vis: list[VerbalIllustration] | None, indent=0) -> list[Line]: assert vis is not None r = Resources() lines: list[Line] = [] for vi in vis: line = Line() - frag = Fragment(vi['t'], r.textFont, color=r.subduedColor) + frag = Fragment(vi["t"], r.textFont, color=r.subduedColor) if indent > 0: frag.setIndent(indent) line.addFragment(frag) lines.append(line) - if 'aq' in vi: - lines += do_aq(trycast(AttributionOfQuote, vi['aq'])) + if "aq" in vi: + lines += do_aq(trycast(AttributionOfQuote, vi["aq"])) return lines -def do_uns(uns: list[list[list[Pair]]]|None, indent:int) -> tuple[list[Fragment], list[Line]]: + +def do_uns( + uns: list[list[list[Pair]]] | None, indent: int +) -> tuple[list[Fragment], list[Line]]: assert uns is not None r = Resources() frags: list[Fragment] = [] @@ -300,17 +361,24 @@ def do_uns(uns: list[list[list[Pair]]]|None, indent:int) -> tuple[list[Fragment] for note in uns: for entry in note: for pair in entry: - if pair['objType'] == 'text': - frag = Fragment('\u2192 '+pair['obj'], r.textFont, color=r.baseColor) + if pair["objType"] == "text": + frag = Fragment( + "\u2192 " + pair["obj"], r.textFont, color=r.baseColor + ) frag.setIndent(indent) frags.append(frag) - elif pair['objType'] == 'vis': - lines += do_vis(trycast(list[VerbalIllustration], pair['obj']), indent) - elif pair['objType'] == 'ri': + elif pair["objType"] == "vis": + lines += do_vis( + trycast(list[VerbalIllustration], pair["obj"]), indent + ) + elif pair["objType"] == "ri": raise NotImplementedError("NO ri") return (frags, lines) -def do_dt(dt: list[list[Pair]]|None, indent: int) -> tuple[list[Fragment], list[Line]]: + +def do_dt( + dt: list[list[Pair]] | None, indent: int +) -> tuple[list[Fragment], list[Line]]: assert dt is not None frags: list[Fragment] = [] lines: list[Line] = [] @@ -318,8 +386,8 @@ def do_dt(dt: list[list[Pair]]|None, indent: int) -> tuple[list[Fragment], list[ first = True for entry in dt: for pair in entry: - if pair['objType'] == 'text': - frag = Fragment(pair['obj'], r.textFont, color=r.baseColor) + if pair["objType"] == "text": + frag = Fragment(pair["obj"], r.textFont, color=r.baseColor) frag.setIndent(indent) if first: frags.append(frag) @@ -327,73 +395,93 @@ def do_dt(dt: list[list[Pair]]|None, indent: int) -> tuple[list[Fragment], list[ line = Line() line.addFragment(frag) lines.append(line) - elif pair['objType'] == 'vis': - lines += do_vis(trycast(list[VerbalIllustration], pair['obj']),indent) - elif pair['objType'] == 'uns': - (newFrags,newLines) = do_uns(trycast(list[list[list[Pair]]], pair['obj']),indent) + elif pair["objType"] == "vis": + lines += do_vis( + trycast(list[VerbalIllustration], pair["obj"]), indent + ) + elif pair["objType"] == "uns": + (newFrags, newLines) = do_uns( + trycast(list[list[list[Pair]]], pair["obj"]), indent + ) frags += newFrags lines += newLines else: print(json.dumps(pair, indent=2)) - raise NotImplementedError(f"Unknown or unimplimented element {pair['objType']}") + raise NotImplementedError( + f"Unknown or unimplimented element {pair['objType']}" + ) first = False return (frags, lines) -def do_sense(sense: Sense|None, indent:int=3) -> tuple[list[Fragment], list[Line]]: + +def do_sense( + sense: Sense | None, indent: int = 3 +) -> tuple[list[Fragment], list[Line]]: if sense is None: - return ([],[]) + return ([], []) lines: list[Line] = [] frags: list[Fragment] = [] r = Resources() - for k,v in sense.items(): - if k == 'sn': + for k, v in sense.items(): + if k == "sn": continue - elif k == 'dt': - (newFrags, newLines) = do_dt(trycast(list[list[Pair]], sense['dt']), indent) + elif k == "dt": + (newFrags, newLines) = do_dt( + trycast(list[list[Pair]], sense["dt"]), indent + ) frags += newFrags lines += newLines - elif k == 'sdsense': + elif k == "sdsense": # XXX - This needs to expand to handle et, ins, lbs, prs, sgram, sls, vrs sdsense = trycast(DividedSense, v) assert sdsense is not None - frag = Fragment(sdsense['sd']+' ', r.italicFont, color=r.baseColor) + frag = Fragment( + sdsense["sd"] + " ", r.italicFont, color=r.baseColor + ) frag.setIndent(indent) line = Line() line.addFragment(frag) - (newFrags, newLines) = do_dt(trycast(list[list[Pair]], sdsense['dt']), indent=indent) + (newFrags, newLines) = do_dt( + trycast(list[list[Pair]], sdsense["dt"]), indent=indent + ) line.addFragment(newFrags) lines.append(line) lines += newLines - elif k == 'sls': + elif k == "sls": labels = trycast(list[str], v) assert labels is not None - frag = Fragment(", ".join(labels)+' ', r.boldFont, color=r.subduedColor) + frag = Fragment( + ", ".join(labels) + " ", r.boldFont, color=r.subduedColor + ) frag.setIndent(indent) frag.setBackground(r.subduedBackground) frags.append(frag) else: - print(k,v) + print(k, v) raise NotImplementedError(f"Unknown or unimplimented element {k}") return (frags, lines) -def do_pseq(inner: int, - outer: int, - pseq: list[Any] ) -> tuple[list[Fragment], list[Line]]: + +def do_pseq( + inner: int, outer: int, pseq: list[Any] +) -> tuple[list[Fragment], list[Line]]: lines: list[Line] = [] frags: list[Fragment] = [] - indent = 3 # XXX - Should this be a parameter passed in? + indent = 3 # XXX - Should this be a parameter passed in? count = 1 r = Resources() newLine = False for entry in pseq: for pair in entry: - if pair['objType'] == 'bs': - sense = pair['obj']['sense'] - (newFrags, newLines) = do_sense(trycast(Sense, sense),indent=indent) + if pair["objType"] == "bs": + sense = pair["obj"]["sense"] + (newFrags, newLines) = do_sense( + trycast(Sense, sense), indent=indent + ) frags += newFrags lines += newLines newLine = True - elif pair['objType'] == 'sense': + elif pair["objType"] == "sense": frag = Fragment(f"({count})", r.textFont, color=r.baseColor) frag.setIndent(indent) if newLine: @@ -401,7 +489,9 @@ def do_pseq(inner: int, line.addFragment(frag) else: frags.append(frag) - (newFrags, newLines) = do_sense(trycast(Sense, pair['obj']), indent=indent+1) + (newFrags, newLines) = do_sense( + trycast(Sense, pair["obj"]), indent=indent + 1 + ) if newLine: line.addFragment(newFrags) lines.append(line) @@ -411,139 +501,156 @@ def do_pseq(inner: int, lines += newLines count += 1 else: - raise NotImplementedError(f"Unknown object type {pair['objType']}") + raise NotImplementedError( + f"Unknown object type {pair['objType']}" + ) return (frags, lines) -def do_sseq(sseq:list[list[list[Pair]]]) -> list[Line]: + +def do_sseq(sseq: list[list[list[Pair]]]) -> list[Line]: lines: list[Line] = [] r = Resources() for outer, item_o in enumerate(sseq): line = Line() - frag =Fragment(str(outer+1), r.boldFont, color=r.baseColor) + frag = Fragment(str(outer + 1), r.boldFont, color=r.baseColor) frag.setIndent(1) line.addFragment(frag) for inner, item_i in enumerate(item_o): indent = 2 if len(item_o) > 1: - frag =Fragment(chr(ord('a')+inner), r.boldFont, color=r.baseColor) + frag = Fragment( + chr(ord("a") + inner), r.boldFont, color=r.baseColor + ) frag.setIndent(2) line.addFragment(frag) indent = 3 for pair in item_i: - objType = pair['objType'] - if objType == 'sense': - sense = trycast(Sense, pair['obj']) + objType = pair["objType"] + if objType == "sense": + sense = trycast(Sense, pair["obj"]) (frags, newlines) = do_sense(sense, indent=indent) line.addFragment(frags) lines.append(line) line = Line() lines += newlines - elif objType == 'sen': + elif objType == "sen": raise NotImplementedError(f"sen unimplimented") - elif objType == 'pseq': - (frags, newlines) = do_pseq(inner, outer, pair['obj']) + elif objType == "pseq": + (frags, newlines) = do_pseq(inner, outer, pair["obj"]) line.addFragment(frags) lines.append(line) line = Line() lines += newlines - elif objType == 'bs': - sense = pair['obj']['sense'] - (newFrags, newLines) = do_sense(trycast(Sense, sense),indent=indent) + elif objType == "bs": + sense = pair["obj"]["sense"] + (newFrags, newLines) = do_sense( + trycast(Sense, sense), indent=indent + ) line.addFragment(newFrags) lines.append(line) line = Line() lines += newLines else: - raise NotImplementedError(f"Unknown object[{objType}] for \n{json.dumps(pair['obj'],indent=2)}") + raise NotImplementedError( + f"Unknown object[{objType}] for \n{json.dumps(pair['obj'],indent=2)}" + ) return lines -def do_ins(inflections:list[Inflection]|None) -> list[Fragment]: + +def do_ins(inflections: list[Inflection] | None) -> list[Fragment]: assert inflections is not None r = Resources() frags: list[Fragment] = [] - sep = '' + sep = "" for inflection in inflections: - if sep == '; ': - frag = Fragment('; ', font=r.boldFont, color=r.baseColor) + if sep == "; ": + frag = Fragment("; ", font=r.boldFont, color=r.baseColor) frags.append(frag) - elif sep != '': + elif sep != "": frag = Fragment(sep, font=r.italicFont, color=r.baseColor) frags.append(frag) - if 'ifc' in inflection: - text = inflection['ifc'] - elif 'if' in inflection: - text = inflection['if'] + if "ifc" in inflection: + text = inflection["ifc"] + elif "if" in inflection: + text = inflection["if"] else: raise ValueError(f"Missing 'if' or 'ifc' in {inflection}") frag = Fragment(text, r.boldFont, color=r.baseColor) frags.append(frag) - sep = '; ' - if 'il' in inflection: - sep = ' ' + inflection['il'] + ' ' - if 'prs' in inflection: - newFrags = do_prs(trycast(list[Pronunciation], inflection['prs'])) + sep = "; " + if "il" in inflection: + sep = " " + inflection["il"] + " " + if "prs" in inflection: + newFrags = do_prs(trycast(list[Pronunciation], inflection["prs"])) frags += newFrags - if 'spl' in inflection: - raise NotImplementedError(f"We haven't implimented 'spl' for inflection: {inflection}") + if "spl" in inflection: + raise NotImplementedError( + f"We haven't implimented 'spl' for inflection: {inflection}" + ) return frags -def do_ets(ets:list[list[Pair]]|None) -> list[Line]: + +def do_ets(ets: list[list[Pair]] | None) -> list[Line]: assert ets is not None r = Resources() lines: list[Line] = [] for et in ets: for pair in et: - if pair['objType'] == 'text': + if pair["objType"] == "text": line = Line() line.addFragment( - Fragment(pair['obj'], r.textFont, color=r.baseColor) + Fragment(pair["obj"], r.textFont, color=r.baseColor) ) lines.append(line) - elif pair['objType'] == 'et_snote': + elif pair["objType"] == "et_snote": line = Line() line.addFragment( - Fragment('Note: '+pair['obj'], r.textFont, color=r.baseColor) + Fragment( + "Note: " + pair["obj"], r.textFont, color=r.baseColor + ) ) lines.append(line) else: - raise NotImplementedError(f"Unknown key {pair['objType']} in et") + raise NotImplementedError( + f"Unknown key {pair['objType']} in et" + ) return lines + def do_def(entry: DefinitionSection) -> list[Line]: assert entry is not None r = Resources() lines: list[Line] = [] - if 'vd' in entry: + if "vd" in entry: line = Line() - line.addFragment( - Fragment(entry['vd'], r.italicFont, color = r.linkColor) - ) + line.addFragment(Fragment(entry["vd"], r.italicFont, color=r.linkColor)) lines.append(line) # # sseg is required # - sseq = entry['sseq'] + sseq = entry["sseq"] lines += do_sseq(sseq) return lines + def getDef(defines: Any) -> list[Line]: Line.setParseText(parseText) workList = restructure(defines) - workList = trycast(list[Definition], workList) - assert workList is not None +# workList = trycast(list[Definition], workList) +# assert workList is not None r = Resources() - lines:list[Line] = [] + lines: list[Line] = [] # # No need to figure it out each time it is used # entries = 0 - id = workList[0]['meta']['id'].lower().split(':')[0] - uses: dict[str,int] = {} + id = workList[0]["meta"]["id"].lower().split(":")[0] + uses: dict[str, int] = {} for entry in workList: - testId = entry['meta']['id'].lower().split(':')[0] + testId = entry["meta"]["id"].lower().split(":")[0] if testId == id: entries += 1 # @@ -551,10 +658,10 @@ def getDef(defines: Any) -> list[Line]: # to capture the count of each FL # try: - uses[entry['fl']] = uses.get(entry['fl'], 0) + 1 + uses[entry["fl"]] = uses.get(entry["fl"], 0) + 1 except KeyError: pass - del(entry) + del entry used: dict[str, int] = {} for k in uses.keys(): used[k] = 0 @@ -562,7 +669,7 @@ def getDef(defines: Any) -> list[Line]: ets: list[Line] = [] for count, work in enumerate(workList): - testId = work['meta']['id'].lower().split(':')[0] + testId = work["meta"]["id"].lower().split(":")[0] # # Skip entries which are not part of the primary definition # @@ -572,25 +679,29 @@ def getDef(defines: Any) -> list[Line]: # Create the First line from the hwi, [ahws] and fl # line = Line() - hwi = trycast(HeadWordInformation, work['hwi']) + hwi = trycast(HeadWordInformation, work["hwi"]) assert hwi is not None - hw = re.sub(r'\*', '', hwi['hw']) + hw = re.sub(r"\*", "", hwi["hw"]) line.addFragment(Fragment(hw, r.headerFont, color=r.baseColor)) - if 'ahws' in work: - ahws = trycast(list[AlternanteHeadword], work['ahws']) + if "ahws" in work: + ahws = trycast(list[AlternanteHeadword], work["ahws"]) assert ahws is not None for ahw in ahws: - hw = re.sub(r'\*', '', ahw['hw']) - line.addFragment(Fragment(', ' + hw, r.headerFont, color=r.baseColor)) - if entries > 1: - frag = Fragment(f" {count + 1} of {entries} ", r.textFont, color= r.subduedColor) + hw = re.sub(r"\*", "", ahw["hw"]) + line.addFragment( + Fragment(", " + hw, r.headerFont, color=r.baseColor) + ) + if entries > 1: + frag = Fragment( + f" {count + 1} of {entries} ", r.textFont, color=r.subduedColor + ) frag.setBackground(r.subduedBackground) line.addFragment(frag) - if 'fl' in work: - text = work['fl'] + if "fl" in work: + text = work["fl"] used[text] += 1 if uses[text] > 1: - text += f' ({used[text]})' + text += f" ({used[text]})" line.addFragment(Fragment(text, r.labelFont, color=r.baseColor)) lines.append(line) @@ -599,48 +710,63 @@ def getDef(defines: Any) -> list[Line]: # While 'prs' is optional, the headword is not. This gets us what we want. # line = Line() - if hwi['hw'].find('*') >= 0: - hw = re.sub(r'\*', '\u00b7', hwi['hw']) - line.addFragment(Fragment(hw + ' ', r.textFont, color=r.subduedColor)) - if 'prs' in hwi: - newFrags = do_prs(trycast(list[Pronunciation], hwi['prs'])) + if hwi["hw"].find("*") >= 0: + hw = re.sub(r"\*", "\u00b7", hwi["hw"]) + line.addFragment( + Fragment(hw + " ", r.textFont, color=r.subduedColor) + ) + if "prs" in hwi: + newFrags = do_prs(trycast(list[Pronunciation], hwi["prs"])) line.addFragment(newFrags) lines.append(line) line = Line() - if 'ins' in work: - inflections = trycast(list[Inflection], work['ins']) + if "ins" in work: + inflections = trycast(list[Inflection], work["ins"]) newFrags = do_ins(inflections) line = Line() line.addFragment(newFrags) lines.append(line) - defines = trycast(list[DefinitionSection], work['def']) + defines = trycast(list[DefinitionSection], work["def"]) assert defines is not None for define in defines: try: lines += do_def(define) except NotImplementedError as e: print(e) - if 'et' in work: + if "et" in work: line = Line() line.addFragment( - Fragment(f"{work['fl']} ({used[work['fl']]})", r.labelFont, color=r.baseColor) + Fragment( + f"{work['fl']} ({used[work['fl']]})", + r.labelFont, + color=r.baseColor, + ) ) ets.append(line) - ets += do_ets(trycast(list[list[Pair]], work['et'])) + ets += do_ets(trycast(list[list[Pair]], work["et"])) for k in work.keys(): - if k not in [ 'meta', 'hom', 'hwi', 'fl', 'def', 'ins', 'prs', 'et', - 'date', 'shortdef']: - #raise NotImplementedError(f"Unknown key {k} in work") + if k not in [ + "meta", + "hom", + "hwi", + "fl", + "def", + "ins", + "prs", + "et", + "date", + "shortdef", + ]: + # raise NotImplementedError(f"Unknown key {k} in work") print(f"Unknown key {k} in work") - if len(ets)>0: + if len(ets) > 0: line = Line() - line.addFragment( - Fragment('Etymology', r.labelFont, color=r.baseColor) - ) + line.addFragment(Fragment("Etymology", r.labelFont, color=r.baseColor)) lines.append(line) - lines+=ets + lines += ets return lines + def parseText(frag: Fragment) -> list[Fragment]: org = frag.text() if frag.asis(): @@ -660,7 +786,7 @@ def parseText(frag: Fragment) -> list[Fragment]: smallCapsFont = QFont(textFont) smallCapsFont.setCapitalization(QFont.Capitalization.SmallCaps) scriptFont = QFont(textFont) - scriptFont.setPixelSize(int(scriptFont.pixelSize()/4)) + scriptFont.setPixelSize(int(scriptFont.pixelSize() / 4)) boldItalicFont = QFont(boldFont) boldItalicFont.setItalic(True) boldSmallCapsFont = QFont(smallCapsFont) @@ -822,4 +948,3 @@ def parseText(frag: Fragment) -> list[Fragment]: raise NotImplementedError( f"Unable to locate a known token {token} in {org}" ) -