This commit is contained in:
Christopher T. Johnson
2024-04-16 11:50:26 -04:00
parent f1ad24d70a
commit 51b1121176
6 changed files with 422 additions and 274 deletions

View File

@@ -1,8 +1,8 @@
# pyright: ignore # pyright: ignore
from .utils import query_error # isort: skip from .utils import query_error # isort: skip
from .books import Book from .books import Book
from .definition import Definition, Fragment, Line
from .person import PersonDialog from .person import PersonDialog
from .read import ReadDialog from .read import ReadDialog
from .session import SessionDialog from .session import SessionDialog
from .words import DefinitionArea, Word from .words import DefinitionArea, Word
from .definition import Fragment, Line, Definition

View File

@@ -1,27 +1,40 @@
import re import re
from typing import Any, Optional, Self, cast, overload from typing import Any, Callable, Optional, Self, cast, overload
import re
from PyQt6.QtCore import QMargins, QPoint, QRect, QSize, QUrl, Qt, pyqtSignal from PyQt6.QtCore import QMargins, QPoint, QRect, QSize, Qt, QUrl, pyqtSignal
from PyQt6.QtGui import QColor, QFont, QFontMetrics, QMouseEvent, QPaintEvent, QPainter, QResizeEvent, QTextOption, QTransform, QBrush from PyQt6.QtGui import (
QBrush,
QColor,
QFont,
QFontMetrics,
QMouseEvent,
QPainter,
QPaintEvent,
QResizeEvent,
QTextOption,
QTransform,
)
from PyQt6.QtWidgets import QWidget from PyQt6.QtWidgets import QWidget
class Fragment: class Fragment:
"""A fragment of text to be displayed""" """A fragment of text to be displayed"""
_indentAmount = 35 _indentAmount = 35
def __init__( def __init__(
self, self,
which: str|Self, which: str | Self,
font: QFont|None = None, font: QFont | None = None,
audio: str = "", audio: str = "",
color: Optional[QColor] = None, color: Optional[QColor] = None,
asis: bool = False, asis: bool = False,
) -> None: ) -> None:
if isinstance(which, Fragment): if isinstance(which, Fragment):
for k,v in which.__dict__.items(): for k, v in which.__dict__.items():
self.__dict__[k] = v self.__dict__[k] = v
return return
self._text:str = which self._text: str = which
if font is None: if font is None:
raise TypeError("Missing required parameter 'font'") raise TypeError("Missing required parameter 'font'")
self._font = font self._font = font
@@ -63,13 +76,14 @@ class Fragment:
return f"({self._position.x()}, {self._position.y()}): {self._text}" return f"({self._position.x()}, {self._position.y()}): {self._text}"
@overload @overload
def paintEvent(self, widthSrc:int) -> QSize: def paintEvent(self, widthSrc: int) -> QSize:
... ...
@overload @overload
def paintEvent(self, widthSrc: QPainter) -> int: def paintEvent(self, widthSrc: QPainter) -> int:
... ...
def paintEvent(self, widthSrc) -> int|QSize: def paintEvent(self, widthSrc: QPainter | int) -> int | QSize:
if isinstance(widthSrc, QPainter): if isinstance(widthSrc, QPainter):
viewportWidth = widthSrc.viewport().width() viewportWidth = widthSrc.viewport().width()
painter = widthSrc painter = widthSrc
@@ -77,28 +91,21 @@ class Fragment:
viewportWidth = widthSrc viewportWidth = widthSrc
painter = None painter = None
fm = QFontMetrics(self._font) fm = QFontMetrics(self._font)
top = ( top = self._position.y() + fm.descent() - fm.height()
self._position.y()
+ fm.descent()
- fm.height()
)
left = self._position.x() left = self._position.x()
width = viewportWidth - left width = viewportWidth - left
height = 2000 height = 2000
rect = QRect(left, top, width, height) rect = QRect(left, top, width, height)
indent = self._indent * self._indentAmount indent = self._indent * self._indentAmount
flags = ( flags = Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignBaseline
Qt.AlignmentFlag.AlignLeft
| Qt.AlignmentFlag.AlignBaseline
)
boundingNoWrap = fm.boundingRect( boundingNoWrap = fm.boundingRect(
rect, flags|Qt.TextFlag.TextSingleLine, self._text rect, flags | Qt.TextFlag.TextSingleLine, self._text
) )
bounding = fm.boundingRect( bounding = fm.boundingRect(
rect, flags|Qt.TextFlag.TextWordWrap, self._text rect, flags | Qt.TextFlag.TextWordWrap, self._text
) )
text = self._text text = self._text
remainingText = '' remainingText = ""
if boundingNoWrap.height() < bounding.height(): if boundingNoWrap.height() < bounding.height():
# #
# This is not optimal, but it is only a few iterations # This is not optimal, but it is only a few iterations
@@ -107,39 +114,29 @@ class Fragment:
char = 0 char = 0
pos = rect.x() pos = rect.x()
while pos < rect.right(): while pos < rect.right():
if text[char] == ' ': if text[char] == " ":
lastSpace = char lastSpace = char
pos += fm.horizontalAdvance( pos += fm.horizontalAdvance(text[char])
text[char]
)
char += 1 char += 1
if lastSpace > 0: if lastSpace > 0:
remainingText = text[lastSpace+1:] remainingText = text[lastSpace + 1 :]
text = text[:lastSpace] text = text[:lastSpace]
size = boundingNoWrap.size() size = boundingNoWrap.size()
boundingNoWrap = fm.boundingRect( boundingNoWrap = fm.boundingRect(
rect, flags|Qt.TextFlag.TextSingleLine, text rect, flags | Qt.TextFlag.TextSingleLine, text
) )
rect.setSize(boundingNoWrap.size()) rect.setSize(boundingNoWrap.size())
if remainingText != "":
if remainingText != '':
top += size.height() top += size.height()
remainingRect = QRect( remainingRect = QRect(indent, top, viewportWidth - indent, height)
indent, top,
viewportWidth - indent, height
)
boundingRemaingRect = fm.boundingRect( boundingRemaingRect = fm.boundingRect(
remainingRect, flags | Qt.TextFlag.TextWordWrap, remainingText remainingRect, flags | Qt.TextFlag.TextWordWrap, remainingText
) )
size = size.grownBy( size = size.grownBy(QMargins(0, 0, 0, boundingRemaingRect.height()))
QMargins(
0,0,0, boundingRemaingRect.height()
)
)
remainingRect.setSize(boundingRemaingRect.size()) remainingRect.setSize(boundingRemaingRect.size())
size = size.grownBy(self._margin) size = size.grownBy(self._margin)
size = size.grownBy(self._border) size = size.grownBy(self._border)
@@ -163,12 +160,14 @@ class Fragment:
brush.setColor(self._background) brush.setColor(self._background)
brush.setStyle(Qt.BrushStyle.SolidPattern) brush.setStyle(Qt.BrushStyle.SolidPattern)
painter.setBrush(brush) painter.setBrush(brush)
painter.fillRect(rect,brush) painter.fillRect(rect, brush)
painter.drawText(rect, flags, text) painter.drawText(rect, flags, text)
if remainingText: if remainingText:
if self._background.isValid(): if self._background.isValid():
painter.fillRect(remainingRect, brush) painter.fillRect(remainingRect, brush)
painter.drawText(remainingRect, flags|Qt.TextFlag.TextWordWrap, remainingText) painter.drawText(
remainingRect, flags | Qt.TextFlag.TextWordWrap, remainingText
)
painter.restore() painter.restore()
return size.height() return size.height()
@@ -310,6 +309,7 @@ class Fragment:
def setBackground(self, color: QColor) -> None: def setBackground(self, color: QColor) -> None:
self._background = color self._background = color
return return
def setIndent(self, indent: int) -> None: def setIndent(self, indent: int) -> None:
self._indent = indent self._indent = indent
return return
@@ -365,8 +365,10 @@ class Fragment:
def pixelIndent(self) -> int: def pixelIndent(self) -> int:
return self._indent * self._indentAmount return self._indent * self._indentAmount
class Line: class Line:
parseText = None parseText = None
def __init__(self) -> None: def __init__(self) -> None:
self._maxHeight = -1 self._maxHeight = -1
self._baseLine = -1 self._baseLine = -1
@@ -379,8 +381,9 @@ class Line:
"|".join([x.text() for x in self._fragments]) "|".join([x.text() for x in self._fragments])
+ f"|{self._maxHeight}" + f"|{self._maxHeight}"
) )
@classmethod @classmethod
def setParseText(cls, call) -> None: def setParseText(cls, call: Callable) -> None:
cls.parseText = call cls.parseText = call
return return
@@ -395,12 +398,16 @@ class Line:
lineSpacing = ls lineSpacing = ls
return lineSpacing return lineSpacing
def addFragment(
def addFragment(self, frags: Fragment|list[Fragment],) -> None: self,
frags: Fragment | list[Fragment],
) -> None:
SPEAKER = "\U0001F508" SPEAKER = "\U0001F508"
if not isinstance(frags, list): if not isinstance(frags, list):
frags = [frags, ] frags = [
frags,
]
for frag in frags: for frag in frags:
if frag.audio().isValid(): if frag.audio().isValid():
frag.setText(frag.text() + " " + SPEAKER) frag.setText(frag.text() + " " + SPEAKER)
@@ -485,6 +492,7 @@ class Line:
def getLineSpacing(self) -> int: def getLineSpacing(self) -> int:
return self._leading + self._maxHeight return self._leading + self._maxHeight
class Definition(QWidget): class Definition(QWidget):
pronounce = pyqtSignal(str) pronounce = pyqtSignal(str)
@@ -499,7 +507,7 @@ class Definition(QWidget):
def setWord(self, word: Any) -> None: def setWord(self, word: Any) -> None:
self._word = word self._word = word
lines:list[Line] = word.get_def() lines: list[Line] = word.get_def()
assert lines is not None assert lines is not None
self._lines = lines self._lines = lines
self._buttons: list[Fragment] = [] self._buttons: list[Fragment] = []

View File

@@ -101,7 +101,7 @@ class SoundOff(QObject):
@pyqtSlot(QMediaPlayer.MediaStatus) @pyqtSlot(QMediaPlayer.MediaStatus)
def mediaStatus(self, status: QMediaPlayer.MediaStatus) -> None: def mediaStatus(self, status: QMediaPlayer.MediaStatus) -> None:
#print(f"mediaStatus: {status}") # print(f"mediaStatus: {status}")
if status == QMediaPlayer.MediaStatus.LoadedMedia: if status == QMediaPlayer.MediaStatus.LoadedMedia:
player: Optional[QMediaPlayer] = cast(QMediaPlayer, self.sender()) player: Optional[QMediaPlayer] = cast(QMediaPlayer, self.sender())
assert player is not None assert player is not None
@@ -110,7 +110,7 @@ class SoundOff(QObject):
@pyqtSlot(QMediaPlayer.PlaybackState) @pyqtSlot(QMediaPlayer.PlaybackState)
def playbackState(self, state: QMediaPlayer.PlaybackState) -> None: def playbackState(self, state: QMediaPlayer.PlaybackState) -> None:
#print(f"playbackState: {state}") # print(f"playbackState: {state}")
return return
# #
@@ -164,7 +164,7 @@ class SoundOff(QObject):
continue continue
self._storage[player] = QByteArray(storage) self._storage[player] = QByteArray(storage)
crypto.addData(self._storage[player]) crypto.addData(self._storage[player])
#print(player, crypto.result().toHex()) # print(player, crypto.result().toHex())
crypto.reset() crypto.reset()
self._buffer[player] = QBuffer(self._storage[player]) self._buffer[player] = QBuffer(self._storage[player])
url = reply.request().url() url = reply.request().url()
@@ -172,5 +172,5 @@ class SoundOff(QObject):
player.setPosition(0) player.setPosition(0)
if player.mediaStatus() == QMediaPlayer.MediaStatus.LoadedMedia: if player.mediaStatus() == QMediaPlayer.MediaStatus.LoadedMedia:
player.play() player.play()
#print("play") # print("play")
return return

View File

@@ -22,6 +22,7 @@ def query_error(query: QSqlQuery) -> NoReturn:
) )
raise Exception(translate("MainWindow", "SQL Error")) raise Exception(translate("MainWindow", "SQL Error"))
class Resources: class Resources:
_instance = None _instance = None
nam = QNetworkAccessManager() nam = QNetworkAccessManager()

View File

@@ -1,35 +1,41 @@
import importlib import importlib
import pkgutil
import json 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 ( from PyQt6.QtCore import Qt, pyqtSlot
Qt,
pyqtSlot,
)
from PyQt6.QtSql import QSqlQuery from PyQt6.QtSql import QSqlQuery
from PyQt6.QtWidgets import QScrollArea from PyQt6.QtWidgets import QScrollArea
from trycast import trycast
from lib.utils import query_error
from lib.sounds import SoundOff
from lib.definition import Definition, Line
import plugins import plugins
def find_plugins(ns_pkg): from lib.definition import Definition, Line
return pkgutil.iter_modules(ns_pkg.__path__, ns_pkg.__name__ + '.') 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 = { discovered_plugins = {
# finder, name, ispkg # 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}" API = "https://api.dictionaryapi.dev/api/v2/entries/en/{word}"
class WordType(TypedDict): class WordType(TypedDict):
word: str word: str
source: str source: str
definition: str definition: str
class Word: class Word:
"""All processing of a dictionary word.""" """All processing of a dictionary word."""
@@ -79,30 +85,38 @@ class Word:
@pyqtSlot() @pyqtSlot()
def playSound(self) -> None: 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(): if url.isValid():
snd = SoundOff() snd = SoundOff()
snd.playSound(url) snd.playSound(url)
return return
def playPRS(self) -> None:
return
def getWord(self) -> str: def getWord(self) -> str:
return cast(str, self.current["word"]) return self.current["word"]
def get_html(self) -> str | None: def get_html(self) -> str | None:
src = self.current['source'] src = self.current["source"]
try: try:
return discovered_plugins[src].getHtml(self.current) return cast(str, discovered_plugins[src].getHtml(self.current))
except KeyError: except KeyError:
raise Exception(f"Unknown source: {src}") raise Exception(f"Unknown source: {src}")
def get_def(self) -> list[Line]: def get_def(self) -> list[Line]:
src = self.current['source'] src = self.current["source"]
try: try:
lines = discovered_plugins[src].getDef(self.current["definition"]) lines = discovered_plugins[src].getDef(self.current["definition"])
lines = trycast(list[Line], lines)
assert lines is not None
return lines return lines
except KeyError: except KeyError:
raise Exception(f"Unknown source: {self.current['source']}") raise Exception(f"Unknown source: {self.current['source']}")
class DefinitionArea(QScrollArea): class DefinitionArea(QScrollArea):
def __init__(self, w: Word, *args: Any, **kwargs: Any) -> None: def __init__(self, w: Word, *args: Any, **kwargs: Any) -> None:
super(DefinitionArea, self).__init__(*args, *kwargs) super(DefinitionArea, self).__init__(*args, *kwargs)

View File

@@ -1,23 +1,25 @@
from PyQt6.QtGui import QColor, QFont
from trycast import trycast
import json import json
import re import re
from typing import Any, Literal, NotRequired, TypedDict, cast 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 PyQt6.QtNetwork import QNetworkRequest
from trycast import trycast
from lib.definition import Fragment, Line
from lib.utils import Resources from lib.utils import Resources
from lib.definition import Line, Fragment
registration = { registration = {
'source': 'mw', "source": "mw",
'name': 'Merriam-Webster', "name": "Merriam-Webster",
'language': 'en-us', "language": "en-us",
} }
API = "https://www.dictionaryapi.com/api/v3/references/collegiate/json/{word}?key={key}" API = "https://www.dictionaryapi.com/api/v3/references/collegiate/json/{word}?key={key}"
key = "51d9df34-ee13-489e-8656-478c215e846c" key = "51d9df34-ee13-489e-8656-478c215e846c"
class Meta(TypedDict): class Meta(TypedDict):
id: str id: str
uuid: str uuid: str
@@ -27,11 +29,13 @@ class Meta(TypedDict):
stems: list[str] stems: list[str]
offensive: bool offensive: bool
class Sound(TypedDict): class Sound(TypedDict):
audio: str audio: str
ref: str ref: str
stat: str stat: str
class Pronunciation(TypedDict): class Pronunciation(TypedDict):
mw: str mw: str
l: NotRequired[str] l: NotRequired[str]
@@ -39,41 +43,52 @@ class Pronunciation(TypedDict):
pun: NotRequired[str] pun: NotRequired[str]
sound: NotRequired[Sound] sound: NotRequired[Sound]
class SubSource(TypedDict): class SubSource(TypedDict):
source: NotRequired[str] source: NotRequired[str]
aqdate: NotRequired[str] aqdate: NotRequired[str]
class AttributionOfQuote(TypedDict): class AttributionOfQuote(TypedDict):
auth: NotRequired[str] auth: NotRequired[str]
source: NotRequired[str] source: NotRequired[str]
aqdate: NotRequired[str] aqdate: NotRequired[str]
subsource: NotRequired[SubSource] subsource: NotRequired[SubSource]
class VerbalIllustration(TypedDict): class VerbalIllustration(TypedDict):
t: str t: str
aq: NotRequired[AttributionOfQuote] aq: NotRequired[AttributionOfQuote]
class HeadWordInformation(TypedDict): class HeadWordInformation(TypedDict):
hw: str hw: str
prs: NotRequired[list[Pronunciation]] prs: NotRequired[list[Pronunciation]]
class AlternanteHeadword(TypedDict): class AlternanteHeadword(TypedDict):
hw: str hw: str
psl: NotRequired[str] psl: NotRequired[str]
class Variant(TypedDict): class Variant(TypedDict):
va: str va: str
vl: NotRequired[str] vl: NotRequired[str]
prs: NotRequired[list[Pronunciation]] prs: NotRequired[list[Pronunciation]]
spl: NotRequired[str] spl: NotRequired[str]
Inflection = TypedDict('Inflection', {
'if': NotRequired[str], Inflection = TypedDict(
'ifc': NotRequired[str], "Inflection",
'il': NotRequired[str], {
'prs': NotRequired[list[Pronunciation]], "if": NotRequired[str],
'spl': NotRequired[str] "ifc": NotRequired[str],
}) "il": NotRequired[str],
"prs": NotRequired[list[Pronunciation]],
"spl": NotRequired[str],
},
)
class CrossReferenceTarget(TypedDict): class CrossReferenceTarget(TypedDict):
cxl: str cxl: str
@@ -81,14 +96,17 @@ class CrossReferenceTarget(TypedDict):
cxt: str cxt: str
cxn: NotRequired[str] cxn: NotRequired[str]
class CognateCrossRef(TypedDict): class CognateCrossRef(TypedDict):
cxl: str cxl: str
cxtis: list[CrossReferenceTarget] cxtis: list[CrossReferenceTarget]
class Pair(TypedDict): class Pair(TypedDict):
objType: str objType: str
obj: Any obj: Any
class DividedSense(TypedDict): class DividedSense(TypedDict):
sd: str sd: str
dt: list[list[Pair]] dt: list[list[Pair]]
@@ -100,6 +118,7 @@ class DividedSense(TypedDict):
sls: NotRequired[list[str]] sls: NotRequired[list[str]]
vrs: NotRequired[list[Variant]] vrs: NotRequired[list[Variant]]
class Sense(TypedDict): class Sense(TypedDict):
dt: list[list[Pair]] dt: list[list[Pair]]
et: NotRequired[list[Pair]] et: NotRequired[list[Pair]]
@@ -112,55 +131,89 @@ class Sense(TypedDict):
sn: NotRequired[str] sn: NotRequired[str]
vrs: NotRequired[list[Variant]] vrs: NotRequired[list[Variant]]
class TruncatedSense(Sense): pass
class TruncatedSense(Sense):
pass
class BindingSubstitutePair(TypedDict): class BindingSubstitutePair(TypedDict):
objType: Literal['bs'] objType: Literal["bs"]
obj: Sense obj: Sense
class SensePair(TypedDict): class SensePair(TypedDict):
objType: Literal['sense'] objType: Literal["sense"]
obj: Sense obj: Sense
class DefinitionSection(TypedDict): class DefinitionSection(TypedDict):
vd: NotRequired[str] vd: NotRequired[str]
sls: NotRequired[list[str]] sls: NotRequired[list[str]]
sseq: Any # list[list[Pair]] sseq: Any # list[list[Pair]]
Definition =TypedDict('Definition', {
'meta': Meta, Definition = TypedDict(
'hom': NotRequired[int], "Definition",
'hwi': HeadWordInformation, {
'ahws': NotRequired[list[AlternanteHeadword]], "ahws": NotRequired[list[AlternanteHeadword]],
'vrs': NotRequired[list[Variant]], "cxs": NotRequired[list[CognateCrossRef]],
'fl': str, "date": NotRequired[str],
'lbs': NotRequired[list[str]], "def": list[DefinitionSection],
'sls': NotRequired[list[str]], "dros": NotRequired[Any],
'ins': NotRequired[list[Inflection]], "et": NotRequired[list[Pair]],
'cxs': NotRequired[list[CognateCrossRef]], "fl": str,
'def': list[DefinitionSection], "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]: def make_pairs(src: list[Any]) -> list[Pair]:
result:list[Pair] = [] result: list[Pair] = []
iters = [iter(src)]*2 iters = [iter(src)] * 2
for entry in zip(*iters): for entry in zip(*iters):
pair0 = { 'objType': entry[0], pair0 = {
'obj': entry[1], "objType": entry[0],
"obj": entry[1],
} }
if isinstance(pair0['obj'], list): if isinstance(pair0["obj"], list):
result.append(cast(Pair,pair0)) result.append(cast(Pair, pair0))
continue continue
pair1 = trycast(Pair, pair0) pair1 = trycast(Pair, pair0)
if pair1 is None: if pair1 is None:
print(pair0['objType'], type(pair0['obj']), print(
json.dumps(pair0['obj'],indent=2) pair0["objType"],
type(pair0["obj"]),
json.dumps(pair0["obj"], indent=2),
) )
assert pair1 is not None assert pair1 is not None
result.append(pair1) result.append(pair1)
return result 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: def restructure(obj: Any) -> Any:
if isinstance(obj, list): if isinstance(obj, list):
if len(obj) == 0: if len(obj) == 0:
@@ -169,16 +222,16 @@ def restructure(obj: Any) -> Any:
pairs = make_pairs(obj) pairs = make_pairs(obj)
result = [] result = []
for pair in pairs: for pair in pairs:
if isinstance(pair['obj'], list): if isinstance(pair["obj"], list):
r2 = [] r2 = []
for item in pair['obj']: for item in pair["obj"]:
r2.append(restructure(item)) r2.append(restructure(item))
pair['obj'] = r2 pair["obj"] = r2
elif isinstance(pair['obj'], dict): elif isinstance(pair["obj"], dict):
r2 = {} r2 = {}
for k,v in pair['obj'].items(): for k, v in pair["obj"].items():
r2[k] = restructure(v) r2[k] = restructure(v)
pair['obj'] = r2 pair["obj"] = r2
result.append(pair) result.append(pair)
return result return result
result = [] result = []
@@ -188,18 +241,20 @@ def restructure(obj: Any) -> Any:
elif isinstance(obj, dict): elif isinstance(obj, dict):
obj2 = cast(dict, obj) obj2 = cast(dict, obj)
result = {} result = {}
for k,v in obj2.items(): for k, v in obj2.items():
result[k] = restructure(v) result[k] = restructure(v)
return result return result
else: else:
return obj return obj
class WordType(TypedDict): class WordType(TypedDict):
word: str word: str
source: str source: str
definition: Any definition: Any
def fetch(word:str) -> WordType:
def fetch(word: str) -> WordType:
request = QNetworkRequest() request = QNetworkRequest()
url = QUrl(API.format(word=word, key=key)) url = QUrl(API.format(word=word, key=key))
request.setUrl(url) request.setUrl(url)
@@ -210,17 +265,18 @@ def fetch(word:str) -> WordType:
reply.finished.connect(loop.quit) reply.finished.connect(loop.quit)
loop.exec() loop.exec()
content = reply.readAll() content = reply.readAll()
data = json.loads(content.data().decode('utf-8')) data = json.loads(content.data().decode("utf-8"))
return { return {
'word': word, "word": word,
'source': 'mw', "source": "mw",
'definition': data, "definition": data,
} }
def soundUrl(sound:Sound, fmt='ogg') -> QUrl:
def soundUrl(sound: Sound, fmt="ogg") -> QUrl:
"""Create a URL from a PRS structure.""" """Create a URL from a PRS structure."""
base = f"https://media.merriam-webster.com/audio/prons/en/us/{fmt}" 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) m = re.match(r"(bix|gg|[a-zA-Z])", audio)
if m: if m:
url = base + f"/{m.group(1)}/" url = base + f"/{m.group(1)}/"
@@ -229,6 +285,7 @@ def soundUrl(sound:Sound, fmt='ogg') -> QUrl:
url += audio + f".{fmt}" url += audio + f".{fmt}"
return QUrl(url) return QUrl(url)
def getFirstSound(definition: Any) -> QUrl: def getFirstSound(definition: Any) -> QUrl:
# ahws, cats, dros, hwi, ins, ri, sdsense, sen, sense, uros, vrs # ahws, cats, dros, hwi, ins, ri, sdsense, sen, sense, uros, vrs
for entry in definition: for entry in definition:
@@ -236,14 +293,15 @@ def getFirstSound(definition: Any) -> QUrl:
hwi = v # trycast hwi = v # trycast
if hwi is None: if hwi is None:
continue continue
if 'prs' in hwi: if "prs" in hwi:
for pr in hwi['prs']: for pr in hwi["prs"]:
if 'sound' in pr: if "sound" in pr:
url = soundUrl(pr['sound']) url = soundUrl(pr["sound"])
if url.isValid(): if url.isValid():
return url return url
return QUrl() return QUrl()
def do_prs(prs: list[Pronunciation] | None) -> list[Fragment]: def do_prs(prs: list[Pronunciation] | None) -> list[Fragment]:
assert prs is not None assert prs is not None
r = Resources() r = Resources()
@@ -253,46 +311,49 @@ def do_prs(prs: list[Pronunciation] | None) -> list[Fragment]:
subduedColor = r.subduedColor subduedColor = r.subduedColor
for pr in prs: for pr in prs:
if 'pun' in pr: if "pun" in pr:
pun = pr['pun'] pun = pr["pun"]
else: else:
pun = ' ' pun = " "
if 'l' in pr: if "l" in pr:
frags.append( 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) frag = Fragment(pr["mw"], font, color=subduedColor)
if 'sound' in pr: if "sound" in pr:
frag.setAudio(soundUrl(pr['sound'])) frag.setAudio(soundUrl(pr["sound"]))
frag.setColor(linkColor) frag.setColor(linkColor)
frags.append(frag) frags.append(frag)
frags.append(Fragment(' ', r.phonicFont)) frags.append(Fragment(" ", r.phonicFont))
if 'l2' in pr: if "l2" in pr:
frags.append( frags.append(Fragment(pun + pr["l2"], font, color=subduedColor))
Fragment(pun + pr['l2'], font, color=subduedColor)
)
return frags return frags
def do_aq(aq: AttributionOfQuote|None) -> list[Line]:
def do_aq(aq: AttributionOfQuote | None) -> list[Line]:
assert aq is not None assert aq is not None
return [] 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 assert vis is not None
r = Resources() r = Resources()
lines: list[Line] = [] lines: list[Line] = []
for vi in vis: for vi in vis:
line = Line() line = Line()
frag = Fragment(vi['t'], r.textFont, color=r.subduedColor) frag = Fragment(vi["t"], r.textFont, color=r.subduedColor)
if indent > 0: if indent > 0:
frag.setIndent(indent) frag.setIndent(indent)
line.addFragment(frag) line.addFragment(frag)
lines.append(line) lines.append(line)
if 'aq' in vi: if "aq" in vi:
lines += do_aq(trycast(AttributionOfQuote, vi['aq'])) lines += do_aq(trycast(AttributionOfQuote, vi["aq"]))
return lines 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 assert uns is not None
r = Resources() r = Resources()
frags: list[Fragment] = [] 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 note in uns:
for entry in note: for entry in note:
for pair in entry: for pair in entry:
if pair['objType'] == 'text': if pair["objType"] == "text":
frag = Fragment('\u2192 '+pair['obj'], r.textFont, color=r.baseColor) frag = Fragment(
"\u2192 " + pair["obj"], r.textFont, color=r.baseColor
)
frag.setIndent(indent) frag.setIndent(indent)
frags.append(frag) frags.append(frag)
elif pair['objType'] == 'vis': elif pair["objType"] == "vis":
lines += do_vis(trycast(list[VerbalIllustration], pair['obj']), indent) lines += do_vis(
elif pair['objType'] == 'ri': trycast(list[VerbalIllustration], pair["obj"]), indent
)
elif pair["objType"] == "ri":
raise NotImplementedError("NO ri") raise NotImplementedError("NO ri")
return (frags, lines) 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 assert dt is not None
frags: list[Fragment] = [] frags: list[Fragment] = []
lines: list[Line] = [] lines: list[Line] = []
@@ -318,8 +386,8 @@ def do_dt(dt: list[list[Pair]]|None, indent: int) -> tuple[list[Fragment], list[
first = True first = True
for entry in dt: for entry in dt:
for pair in entry: for pair in entry:
if pair['objType'] == 'text': if pair["objType"] == "text":
frag = Fragment(pair['obj'], r.textFont, color=r.baseColor) frag = Fragment(pair["obj"], r.textFont, color=r.baseColor)
frag.setIndent(indent) frag.setIndent(indent)
if first: if first:
frags.append(frag) frags.append(frag)
@@ -327,58 +395,76 @@ def do_dt(dt: list[list[Pair]]|None, indent: int) -> tuple[list[Fragment], list[
line = Line() line = Line()
line.addFragment(frag) line.addFragment(frag)
lines.append(line) lines.append(line)
elif pair['objType'] == 'vis': elif pair["objType"] == "vis":
lines += do_vis(trycast(list[VerbalIllustration], pair['obj']),indent) lines += do_vis(
elif pair['objType'] == 'uns': trycast(list[VerbalIllustration], pair["obj"]), indent
(newFrags,newLines) = do_uns(trycast(list[list[list[Pair]]], pair['obj']),indent) )
elif pair["objType"] == "uns":
(newFrags, newLines) = do_uns(
trycast(list[list[list[Pair]]], pair["obj"]), indent
)
frags += newFrags frags += newFrags
lines += newLines lines += newLines
else: else:
print(json.dumps(pair, indent=2)) 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 first = False
return (frags, lines) 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: if sense is None:
return ([],[]) return ([], [])
lines: list[Line] = [] lines: list[Line] = []
frags: list[Fragment] = [] frags: list[Fragment] = []
r = Resources() r = Resources()
for k,v in sense.items(): for k, v in sense.items():
if k == 'sn': if k == "sn":
continue continue
elif k == 'dt': elif k == "dt":
(newFrags, newLines) = do_dt(trycast(list[list[Pair]], sense['dt']), indent) (newFrags, newLines) = do_dt(
trycast(list[list[Pair]], sense["dt"]), indent
)
frags += newFrags frags += newFrags
lines += newLines lines += newLines
elif k == 'sdsense': elif k == "sdsense":
# XXX - This needs to expand to handle et, ins, lbs, prs, sgram, sls, vrs # XXX - This needs to expand to handle et, ins, lbs, prs, sgram, sls, vrs
sdsense = trycast(DividedSense, v) sdsense = trycast(DividedSense, v)
assert sdsense is not None 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) frag.setIndent(indent)
line = Line() line = Line()
line.addFragment(frag) 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) line.addFragment(newFrags)
lines.append(line) lines.append(line)
lines += newLines lines += newLines
elif k == 'sls': elif k == "sls":
labels = trycast(list[str], v) labels = trycast(list[str], v)
assert labels is not None 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.setIndent(indent)
frag.setBackground(r.subduedBackground) frag.setBackground(r.subduedBackground)
frags.append(frag) frags.append(frag)
else: else:
print(k,v) print(k, v)
raise NotImplementedError(f"Unknown or unimplimented element {k}") raise NotImplementedError(f"Unknown or unimplimented element {k}")
return (frags, lines) return (frags, lines)
def do_pseq(inner: int,
outer: int, def do_pseq(
pseq: list[Any] ) -> tuple[list[Fragment], list[Line]]: inner: int, outer: int, pseq: list[Any]
) -> tuple[list[Fragment], list[Line]]:
lines: list[Line] = [] lines: list[Line] = []
frags: list[Fragment] = [] frags: list[Fragment] = []
indent = 3 # XXX - Should this be a parameter passed in? indent = 3 # XXX - Should this be a parameter passed in?
@@ -387,13 +473,15 @@ def do_pseq(inner: int,
newLine = False newLine = False
for entry in pseq: for entry in pseq:
for pair in entry: for pair in entry:
if pair['objType'] == 'bs': if pair["objType"] == "bs":
sense = pair['obj']['sense'] sense = pair["obj"]["sense"]
(newFrags, newLines) = do_sense(trycast(Sense, sense),indent=indent) (newFrags, newLines) = do_sense(
trycast(Sense, sense), indent=indent
)
frags += newFrags frags += newFrags
lines += newLines lines += newLines
newLine = True newLine = True
elif pair['objType'] == 'sense': elif pair["objType"] == "sense":
frag = Fragment(f"({count})", r.textFont, color=r.baseColor) frag = Fragment(f"({count})", r.textFont, color=r.baseColor)
frag.setIndent(indent) frag.setIndent(indent)
if newLine: if newLine:
@@ -401,7 +489,9 @@ def do_pseq(inner: int,
line.addFragment(frag) line.addFragment(frag)
else: else:
frags.append(frag) 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: if newLine:
line.addFragment(newFrags) line.addFragment(newFrags)
lines.append(line) lines.append(line)
@@ -411,139 +501,156 @@ def do_pseq(inner: int,
lines += newLines lines += newLines
count += 1 count += 1
else: else:
raise NotImplementedError(f"Unknown object type {pair['objType']}") raise NotImplementedError(
f"Unknown object type {pair['objType']}"
)
return (frags, lines) 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] = [] lines: list[Line] = []
r = Resources() r = Resources()
for outer, item_o in enumerate(sseq): for outer, item_o in enumerate(sseq):
line = Line() 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) frag.setIndent(1)
line.addFragment(frag) line.addFragment(frag)
for inner, item_i in enumerate(item_o): for inner, item_i in enumerate(item_o):
indent = 2 indent = 2
if len(item_o) > 1: 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) frag.setIndent(2)
line.addFragment(frag) line.addFragment(frag)
indent = 3 indent = 3
for pair in item_i: for pair in item_i:
objType = pair['objType'] objType = pair["objType"]
if objType == 'sense': if objType == "sense":
sense = trycast(Sense, pair['obj']) sense = trycast(Sense, pair["obj"])
(frags, newlines) = do_sense(sense, indent=indent) (frags, newlines) = do_sense(sense, indent=indent)
line.addFragment(frags) line.addFragment(frags)
lines.append(line) lines.append(line)
line = Line() line = Line()
lines += newlines lines += newlines
elif objType == 'sen': elif objType == "sen":
raise NotImplementedError(f"sen unimplimented") raise NotImplementedError(f"sen unimplimented")
elif objType == 'pseq': elif objType == "pseq":
(frags, newlines) = do_pseq(inner, outer, pair['obj']) (frags, newlines) = do_pseq(inner, outer, pair["obj"])
line.addFragment(frags) line.addFragment(frags)
lines.append(line) lines.append(line)
line = Line() line = Line()
lines += newlines lines += newlines
elif objType == 'bs': elif objType == "bs":
sense = pair['obj']['sense'] sense = pair["obj"]["sense"]
(newFrags, newLines) = do_sense(trycast(Sense, sense),indent=indent) (newFrags, newLines) = do_sense(
trycast(Sense, sense), indent=indent
)
line.addFragment(newFrags) line.addFragment(newFrags)
lines.append(line) lines.append(line)
line = Line() line = Line()
lines += newLines lines += newLines
else: 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 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 assert inflections is not None
r = Resources() r = Resources()
frags: list[Fragment] = [] frags: list[Fragment] = []
sep = '' sep = ""
for inflection in inflections: for inflection in inflections:
if sep == '; ': if sep == "; ":
frag = Fragment('; ', font=r.boldFont, color=r.baseColor) frag = Fragment("; ", font=r.boldFont, color=r.baseColor)
frags.append(frag) frags.append(frag)
elif sep != '': elif sep != "":
frag = Fragment(sep, font=r.italicFont, color=r.baseColor) frag = Fragment(sep, font=r.italicFont, color=r.baseColor)
frags.append(frag) frags.append(frag)
if 'ifc' in inflection: if "ifc" in inflection:
text = inflection['ifc'] text = inflection["ifc"]
elif 'if' in inflection: elif "if" in inflection:
text = inflection['if'] text = inflection["if"]
else: else:
raise ValueError(f"Missing 'if' or 'ifc' in {inflection}") raise ValueError(f"Missing 'if' or 'ifc' in {inflection}")
frag = Fragment(text, r.boldFont, color=r.baseColor) frag = Fragment(text, r.boldFont, color=r.baseColor)
frags.append(frag) frags.append(frag)
sep = '; ' sep = "; "
if 'il' in inflection: if "il" in inflection:
sep = ' ' + inflection['il'] + ' ' sep = " " + inflection["il"] + " "
if 'prs' in inflection: if "prs" in inflection:
newFrags = do_prs(trycast(list[Pronunciation], inflection['prs'])) newFrags = do_prs(trycast(list[Pronunciation], inflection["prs"]))
frags += newFrags frags += newFrags
if 'spl' in inflection: if "spl" in inflection:
raise NotImplementedError(f"We haven't implimented 'spl' for inflection: {inflection}") raise NotImplementedError(
f"We haven't implimented 'spl' for inflection: {inflection}"
)
return frags 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 assert ets is not None
r = Resources() r = Resources()
lines: list[Line] = [] lines: list[Line] = []
for et in ets: for et in ets:
for pair in et: for pair in et:
if pair['objType'] == 'text': if pair["objType"] == "text":
line = Line() line = Line()
line.addFragment( line.addFragment(
Fragment(pair['obj'], r.textFont, color=r.baseColor) Fragment(pair["obj"], r.textFont, color=r.baseColor)
) )
lines.append(line) lines.append(line)
elif pair['objType'] == 'et_snote': elif pair["objType"] == "et_snote":
line = Line() line = Line()
line.addFragment( line.addFragment(
Fragment('Note: '+pair['obj'], r.textFont, color=r.baseColor) Fragment(
"Note: " + pair["obj"], r.textFont, color=r.baseColor
)
) )
lines.append(line) lines.append(line)
else: else:
raise NotImplementedError(f"Unknown key {pair['objType']} in et") raise NotImplementedError(
f"Unknown key {pair['objType']} in et"
)
return lines return lines
def do_def(entry: DefinitionSection) -> list[Line]: def do_def(entry: DefinitionSection) -> list[Line]:
assert entry is not None assert entry is not None
r = Resources() r = Resources()
lines: list[Line] = [] lines: list[Line] = []
if 'vd' in entry: if "vd" in entry:
line = Line() line = Line()
line.addFragment( line.addFragment(Fragment(entry["vd"], r.italicFont, color=r.linkColor))
Fragment(entry['vd'], r.italicFont, color = r.linkColor)
)
lines.append(line) lines.append(line)
# #
# sseg is required # sseg is required
# #
sseq = entry['sseq'] sseq = entry["sseq"]
lines += do_sseq(sseq) lines += do_sseq(sseq)
return lines return lines
def getDef(defines: Any) -> list[Line]: def getDef(defines: Any) -> list[Line]:
Line.setParseText(parseText) Line.setParseText(parseText)
workList = restructure(defines) workList = restructure(defines)
workList = trycast(list[Definition], workList) # workList = trycast(list[Definition], workList)
assert workList is not None # assert workList is not None
r = Resources() r = Resources()
lines:list[Line] = [] lines: list[Line] = []
# #
# No need to figure it out each time it is used # No need to figure it out each time it is used
# #
entries = 0 entries = 0
id = workList[0]['meta']['id'].lower().split(':')[0] id = workList[0]["meta"]["id"].lower().split(":")[0]
uses: dict[str,int] = {} uses: dict[str, int] = {}
for entry in workList: for entry in workList:
testId = entry['meta']['id'].lower().split(':')[0] testId = entry["meta"]["id"].lower().split(":")[0]
if testId == id: if testId == id:
entries += 1 entries += 1
# #
@@ -551,10 +658,10 @@ def getDef(defines: Any) -> list[Line]:
# to capture the count of each FL # to capture the count of each FL
# #
try: try:
uses[entry['fl']] = uses.get(entry['fl'], 0) + 1 uses[entry["fl"]] = uses.get(entry["fl"], 0) + 1
except KeyError: except KeyError:
pass pass
del(entry) del entry
used: dict[str, int] = {} used: dict[str, int] = {}
for k in uses.keys(): for k in uses.keys():
used[k] = 0 used[k] = 0
@@ -562,7 +669,7 @@ def getDef(defines: Any) -> list[Line]:
ets: list[Line] = [] ets: list[Line] = []
for count, work in enumerate(workList): 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 # 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 # Create the First line from the hwi, [ahws] and fl
# #
line = Line() line = Line()
hwi = trycast(HeadWordInformation, work['hwi']) hwi = trycast(HeadWordInformation, work["hwi"])
assert hwi is not None 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)) line.addFragment(Fragment(hw, r.headerFont, color=r.baseColor))
if 'ahws' in work: if "ahws" in work:
ahws = trycast(list[AlternanteHeadword], work['ahws']) ahws = trycast(list[AlternanteHeadword], work["ahws"])
assert ahws is not None assert ahws is not None
for ahw in ahws: for ahw in ahws:
hw = re.sub(r'\*', '', ahw['hw']) hw = re.sub(r"\*", "", ahw["hw"])
line.addFragment(Fragment(', ' + hw, r.headerFont, color=r.baseColor)) line.addFragment(
Fragment(", " + hw, r.headerFont, color=r.baseColor)
)
if entries > 1: if entries > 1:
frag = Fragment(f" {count + 1} of {entries} ", r.textFont, color= r.subduedColor) frag = Fragment(
f" {count + 1} of {entries} ", r.textFont, color=r.subduedColor
)
frag.setBackground(r.subduedBackground) frag.setBackground(r.subduedBackground)
line.addFragment(frag) line.addFragment(frag)
if 'fl' in work: if "fl" in work:
text = work['fl'] text = work["fl"]
used[text] += 1 used[text] += 1
if uses[text] > 1: if uses[text] > 1:
text += f' ({used[text]})' text += f" ({used[text]})"
line.addFragment(Fragment(text, r.labelFont, color=r.baseColor)) line.addFragment(Fragment(text, r.labelFont, color=r.baseColor))
lines.append(line) 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. # While 'prs' is optional, the headword is not. This gets us what we want.
# #
line = Line() line = Line()
if hwi['hw'].find('*') >= 0: if hwi["hw"].find("*") >= 0:
hw = re.sub(r'\*', '\u00b7', hwi['hw']) hw = re.sub(r"\*", "\u00b7", hwi["hw"])
line.addFragment(Fragment(hw + ' ', r.textFont, color=r.subduedColor)) line.addFragment(
if 'prs' in hwi: Fragment(hw + " ", r.textFont, color=r.subduedColor)
newFrags = do_prs(trycast(list[Pronunciation], hwi['prs'])) )
if "prs" in hwi:
newFrags = do_prs(trycast(list[Pronunciation], hwi["prs"]))
line.addFragment(newFrags) line.addFragment(newFrags)
lines.append(line) lines.append(line)
line = Line() line = Line()
if 'ins' in work: if "ins" in work:
inflections = trycast(list[Inflection], work['ins']) inflections = trycast(list[Inflection], work["ins"])
newFrags = do_ins(inflections) newFrags = do_ins(inflections)
line = Line() line = Line()
line.addFragment(newFrags) line.addFragment(newFrags)
lines.append(line) lines.append(line)
defines = trycast(list[DefinitionSection], work['def']) defines = trycast(list[DefinitionSection], work["def"])
assert defines is not None assert defines is not None
for define in defines: for define in defines:
try: try:
lines += do_def(define) lines += do_def(define)
except NotImplementedError as e: except NotImplementedError as e:
print(e) print(e)
if 'et' in work: if "et" in work:
line = Line() line = Line()
line.addFragment( 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.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(): for k in work.keys():
if k not in [ 'meta', 'hom', 'hwi', 'fl', 'def', 'ins', 'prs', 'et', if k not in [
'date', 'shortdef']: "meta",
#raise NotImplementedError(f"Unknown key {k} in work") "hom",
"hwi",
"fl",
"def",
"ins",
"prs",
"et",
"date",
"shortdef",
]:
# raise NotImplementedError(f"Unknown key {k} in work")
print(f"Unknown key {k} in work") print(f"Unknown key {k} in work")
if len(ets)>0: if len(ets) > 0:
line = Line() line = Line()
line.addFragment( line.addFragment(Fragment("Etymology", r.labelFont, color=r.baseColor))
Fragment('Etymology', r.labelFont, color=r.baseColor)
)
lines.append(line) lines.append(line)
lines+=ets lines += ets
return lines return lines
def parseText(frag: Fragment) -> list[Fragment]: def parseText(frag: Fragment) -> list[Fragment]:
org = frag.text() org = frag.text()
if frag.asis(): if frag.asis():
@@ -660,7 +786,7 @@ def parseText(frag: Fragment) -> list[Fragment]:
smallCapsFont = QFont(textFont) smallCapsFont = QFont(textFont)
smallCapsFont.setCapitalization(QFont.Capitalization.SmallCaps) smallCapsFont.setCapitalization(QFont.Capitalization.SmallCaps)
scriptFont = QFont(textFont) scriptFont = QFont(textFont)
scriptFont.setPixelSize(int(scriptFont.pixelSize()/4)) scriptFont.setPixelSize(int(scriptFont.pixelSize() / 4))
boldItalicFont = QFont(boldFont) boldItalicFont = QFont(boldFont)
boldItalicFont.setItalic(True) boldItalicFont.setItalic(True)
boldSmallCapsFont = QFont(smallCapsFont) boldSmallCapsFont = QFont(smallCapsFont)
@@ -822,4 +948,3 @@ def parseText(frag: Fragment) -> list[Fragment]:
raise NotImplementedError( raise NotImplementedError(
f"Unable to locate a known token {token} in {org}" f"Unable to locate a known token {token} in {org}"
) )