texdocxdrafter 0.1.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. texdocxdrafter/__init__.py +1 -0
  2. texdocxdrafter/audit.py +113 -0
  3. texdocxdrafter/auxreader.py +84 -0
  4. texdocxdrafter/bbl.py +538 -0
  5. texdocxdrafter/cli.py +227 -0
  6. texdocxdrafter/defaults/macros.yaml +167 -0
  7. texdocxdrafter/expand.py +408 -0
  8. texdocxdrafter/fidelity.py +154 -0
  9. texdocxdrafter/harvest.py +443 -0
  10. texdocxdrafter/inventory.py +61 -0
  11. texdocxdrafter/lexer.py +191 -0
  12. texdocxdrafter/parser/__init__.py +63 -0
  13. texdocxdrafter/parser/blocks.py +728 -0
  14. texdocxdrafter/parser/core.py +332 -0
  15. texdocxdrafter/parser/frontmatter.py +89 -0
  16. texdocxdrafter/parser/inlines.py +213 -0
  17. texdocxdrafter/parser/math.py +90 -0
  18. texdocxdrafter/parser/model.py +298 -0
  19. texdocxdrafter/parser/tables.py +279 -0
  20. texdocxdrafter/pipeline.py +614 -0
  21. texdocxdrafter/preflight.py +541 -0
  22. texdocxdrafter/refdoc.py +411 -0
  23. texdocxdrafter/resolve.py +135 -0
  24. texdocxdrafter/sandbox.py +418 -0
  25. texdocxdrafter/sidecompile.py +107 -0
  26. texdocxdrafter/stylemap.py +136 -0
  27. texdocxdrafter/tikz.py +93 -0
  28. texdocxdrafter/validate.py +228 -0
  29. texdocxdrafter/writer/__init__.py +1368 -0
  30. texdocxdrafter/writer/images.py +108 -0
  31. texdocxdrafter/writer/math.py +171 -0
  32. texdocxdrafter/writer/numbering.py +117 -0
  33. texdocxdrafter/writer/textmath.py +222 -0
  34. texdocxdrafter/writer/xmlutil.py +132 -0
  35. texdocxdrafter-0.1.2.dist-info/METADATA +463 -0
  36. texdocxdrafter-0.1.2.dist-info/RECORD +40 -0
  37. texdocxdrafter-0.1.2.dist-info/WHEEL +5 -0
  38. texdocxdrafter-0.1.2.dist-info/entry_points.txt +2 -0
  39. texdocxdrafter-0.1.2.dist-info/licenses/LICENSE +21 -0
  40. texdocxdrafter-0.1.2.dist-info/top_level.txt +1 -0
@@ -0,0 +1 @@
1
+ __version__ = "0.1.2"
@@ -0,0 +1,113 @@
1
+ """Coverage report: every construct that was dropped, approximated or could not be resolved."""
2
+ from __future__ import annotations
3
+
4
+ from collections import Counter
5
+ from dataclasses import dataclass, field
6
+ from pathlib import Path
7
+
8
+ # kind -> (heading, severity). Severity "info" items are expected presentation drops.
9
+ KINDS = {
10
+ "raw": "Unsupported constructs kept as visible Drafter Todo text",
11
+ "dropped": "Presentation commands dropped",
12
+ "direct-format": "Direct formatting used (no matching Word style)",
13
+ "macro": "Macros that could not be expanded",
14
+ "conditional": "TeX conditionals not resolved",
15
+ "math": "Math kept as LaTeX text",
16
+ "figure": "Figures",
17
+ "label": "Labels and cross-references",
18
+ "numbering": "Numbering differences between LaTeX and Word",
19
+ "harvest": "Citation harvest",
20
+ "table": "Tables",
21
+ "style": "Style map",
22
+ "words": "Word-count check",
23
+ "fidelity": "Content check (tree against docx)",
24
+ "preflight": "Preflight",
25
+ "other": "Other",
26
+ }
27
+
28
+
29
+ @dataclass
30
+ class Entry:
31
+ kind: str
32
+ message: str
33
+ loc: tuple[str, int] | None = None
34
+ severity: str = "warn"
35
+
36
+ def where(self) -> str:
37
+ return f"{self.loc[0]}:{self.loc[1]}" if self.loc else ""
38
+
39
+
40
+ @dataclass
41
+ class Audit:
42
+ entries: list[Entry] = field(default_factory=list)
43
+ census: Counter = field(default_factory=Counter)
44
+ info: dict = field(default_factory=dict)
45
+
46
+ def add(self, kind: str, message: str, loc=None, severity: str = "warn") -> None:
47
+ self.entries.append(Entry(kind, message, loc, severity))
48
+
49
+ def count(self, construct: str, n: int = 1) -> None:
50
+ self.census[construct] += n
51
+
52
+ def of(self, kind: str) -> list[Entry]:
53
+ return [e for e in self.entries if e.kind == kind]
54
+
55
+ def warnings(self) -> list[Entry]:
56
+ return [e for e in self.entries if e.severity != "info"]
57
+
58
+ def render(self, title: str, stylemap: dict | None = None, style_usage: dict | None = None) -> str:
59
+ lines = [f"# texdocxdrafter report: {title}", ""]
60
+ n_warn = len(self.warnings())
61
+ lines.append(f"{n_warn} items need attention; {len(self.entries) - n_warn} informational.")
62
+ lines.append("")
63
+ if self.info:
64
+ lines.append("## Run summary")
65
+ lines.append("")
66
+ for k, v in self.info.items():
67
+ lines.append(f"- {k}: {v}")
68
+ lines.append("")
69
+ if style_usage:
70
+ lines.append("## Style usage")
71
+ lines.append("")
72
+ lines.append("| Style | Paragraphs |")
73
+ lines.append("|---|---|")
74
+ for k, v in sorted(style_usage.items(), key=lambda kv: -kv[1]):
75
+ lines.append(f"| {k} | {v} |")
76
+ lines.append("")
77
+ if self.census:
78
+ lines.append("## Construct census")
79
+ lines.append("")
80
+ lines.append("| Construct | Count |")
81
+ lines.append("|---|---|")
82
+ for k, v in sorted(self.census.items(), key=lambda kv: (-kv[1], kv[0])):
83
+ lines.append(f"| {k} | {v} |")
84
+ lines.append("")
85
+ if stylemap:
86
+ lines.append("## Style map used")
87
+ lines.append("")
88
+ for sect in ("paragraph", "character"):
89
+ lines.append(f"- {sect}: " + ", ".join(f"{k}={v}" for k, v in stylemap[sect].items()))
90
+ lines.append("- options: " + ", ".join(f"{k}={v}" for k, v in stylemap["options"].items()))
91
+ lines.append("")
92
+ by_kind: dict[str, list[Entry]] = {}
93
+ for e in self.entries:
94
+ by_kind.setdefault(e.kind, []).append(e)
95
+ for kind in KINDS:
96
+ items = by_kind.get(kind)
97
+ if not items:
98
+ continue
99
+ lines.append(f"## {KINDS[kind]} ({len(items)})")
100
+ lines.append("")
101
+ if kind == "dropped":
102
+ grouped = Counter(e.message for e in items)
103
+ for msg, n in grouped.most_common():
104
+ first = next(e for e in items if e.message == msg)
105
+ lines.append(f"- {msg} x{n} (first at {first.where()})")
106
+ else:
107
+ for e in items:
108
+ lines.append(f"- {e.where() + ': ' if e.loc else ''}{e.message}")
109
+ lines.append("")
110
+ return "\n".join(lines).rstrip() + "\n"
111
+
112
+ def write(self, path: str | Path, title: str, **kw) -> None:
113
+ Path(path).write_text(self.render(title, **kw), encoding="utf-8")
@@ -0,0 +1,84 @@
1
+ r"""Stage 5b: read labels, kinds and numbers from the .aux file (including the cleveref variant)."""
2
+ from __future__ import annotations
3
+
4
+ import re
5
+ from dataclasses import dataclass
6
+ from pathlib import Path
7
+
8
+ from .expand import read_group
9
+
10
+
11
+ @dataclass
12
+ class AuxLabel:
13
+ key: str
14
+ number: str = ""
15
+ page: str = ""
16
+ title: str = ""
17
+ cref_kind: str | None = None # section table figure equation item …
18
+
19
+
20
+ def _groups(s: str) -> list[str]:
21
+ out: list[str] = []
22
+ i = 0
23
+ while i < len(s):
24
+ if s[i] == "{":
25
+ inner, i = read_group(s, i)
26
+ out.append(inner)
27
+ else:
28
+ i += 1
29
+ return out
30
+
31
+
32
+ MAX_DEPTH = 10
33
+
34
+
35
+ def read_aux(path: Path | None, roots: list[Path] | None = None, _seen: set | None = None,
36
+ _depth: int = 0, issues: list[str] | None = None) -> dict[str, AuxLabel]:
37
+ """Labels of an .aux file and the .aux files it ``\\@input``s, which must lie under ``roots`` (if given).
38
+ Decoding problems are appended to ``issues`` when given."""
39
+ from .preflight import inside, read_source_text
40
+
41
+ labels: dict[str, AuxLabel] = {}
42
+ if path is None or _depth > MAX_DEPTH:
43
+ return labels
44
+ try:
45
+ path = Path(path).resolve(strict=True)
46
+ except OSError:
47
+ return labels
48
+ if not path.is_file() or (roots and not inside(path, roots)):
49
+ return labels
50
+ seen = _seen if _seen is not None else set()
51
+ if path in seen:
52
+ return labels
53
+ seen.add(path)
54
+ for line in read_source_text(path, issues if issues is not None else []).splitlines():
55
+ m = re.match(r"\\newlabel\{([^}]*)\}\{(.*)\}\s*$", line)
56
+ if m:
57
+ key, rest = m.group(1), m.group(2)
58
+ g = _groups("{" + rest + "}")
59
+ fields = _groups(g[0]) if g else []
60
+ if key.endswith("@cref"):
61
+ real = key[:-5]
62
+ lab = labels.setdefault(real, AuxLabel(real))
63
+ km = re.match(r"\[([^\]]*)\]\[[^\]]*\]\[[^\]]*\](.*)", fields[0] if fields else "")
64
+ if km:
65
+ lab.cref_kind = km.group(1)
66
+ continue
67
+ lab = labels.setdefault(key, AuxLabel(key))
68
+ if fields:
69
+ lab.number = _clean(fields[0])
70
+ if len(fields) > 1:
71
+ lab.page = _clean(fields[1])
72
+ if len(fields) > 2:
73
+ lab.title = _clean(fields[2])
74
+ continue
75
+ im = re.match(r"\\@input\{([^}]*)\}", line)
76
+ if im:
77
+ sub = path.parent / im.group(1)
78
+ for k, v in read_aux(sub, roots, seen, _depth + 1, issues).items():
79
+ labels.setdefault(k, v)
80
+ return labels
81
+
82
+
83
+ def _clean(s: str) -> str:
84
+ return re.sub(r"\\[a-zA-Z]+\s*|[{}]", "", s).strip()