Coverage for collectra/types/texts.py: 77.19%
57 statements
« prev ^ index » next coverage.py v7.16.2, created at 2026-10-16 09:56 +0000
« prev ^ index » next coverage.py v7.16.2, created at 2026-10-16 09:56 +0000
1from dataclasses import dataclass, field
2from difflib import SequenceMatcher
3from io import StringIO
4from pathlib import Path
6from markdown import Markdown as MDown
8from .base import Artefact
10__all__ = ["Text"]
13def unmark_element(element, stream=None):
14 if stream is None:
15 stream = StringIO()
16 if element.text:
17 stream.write(element.text)
18 for sub in element:
19 unmark_element(sub, stream)
20 if element.tail:
21 stream.write(element.tail)
22 return stream.getvalue()
25# patching Markdown
26MDown.output_formats["plain"] = unmark_element
27__md = MDown(output_format="plain")
28__md.stripTopLevelTags = False
31def unmark(text):
32 return __md.convert(text)
35@dataclass
36class Text(Artefact):
38 data: str | Path | None = field(default=None)
39 path: str | Path | None = field(default=None)
41 def __post_init__(self):
42 super().__post_init__()
43 if self.path is not None and self.data is not None:
44 raise ValueError("Text accepts either data or path, not both")
45 # Continue accepting legacy file references in data.
46 if self.path is None and self.data:
47 try:
48 if Path(self.data).is_file():
49 self.path, self.data = Path(self.data), None
50 except (OSError, ValueError):
51 pass
52 if self.path is not None:
53 self.path = Path(self.path)
54 self.data = self.path.read_text(encoding="utf-8")
55 elif self.data is None:
56 self.data = ""
58 def serialize(self) -> dict:
59 serialized = super().serialize()
60 if self.path is not None:
61 serialized.pop("data", None)
62 serialized["path"] = str(self.path)
63 else:
64 serialized.pop("path", None)
65 return serialized
67 def __call__(self) -> str:
68 return str(self.data)
70 def display(self, context) -> dict:
71 return context.text(self)
73 def evaluate(self, gold: "Text") -> float:
74 if not isinstance(gold, Text):
75 raise ValueError("Reference data must be an instance of Text.")
76 ratio = SequenceMatcher(None, str(self.data), str(gold.data)).ratio()
77 return ratio