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.
- texdocxdrafter/__init__.py +1 -0
- texdocxdrafter/audit.py +113 -0
- texdocxdrafter/auxreader.py +84 -0
- texdocxdrafter/bbl.py +538 -0
- texdocxdrafter/cli.py +227 -0
- texdocxdrafter/defaults/macros.yaml +167 -0
- texdocxdrafter/expand.py +408 -0
- texdocxdrafter/fidelity.py +154 -0
- texdocxdrafter/harvest.py +443 -0
- texdocxdrafter/inventory.py +61 -0
- texdocxdrafter/lexer.py +191 -0
- texdocxdrafter/parser/__init__.py +63 -0
- texdocxdrafter/parser/blocks.py +728 -0
- texdocxdrafter/parser/core.py +332 -0
- texdocxdrafter/parser/frontmatter.py +89 -0
- texdocxdrafter/parser/inlines.py +213 -0
- texdocxdrafter/parser/math.py +90 -0
- texdocxdrafter/parser/model.py +298 -0
- texdocxdrafter/parser/tables.py +279 -0
- texdocxdrafter/pipeline.py +614 -0
- texdocxdrafter/preflight.py +541 -0
- texdocxdrafter/refdoc.py +411 -0
- texdocxdrafter/resolve.py +135 -0
- texdocxdrafter/sandbox.py +418 -0
- texdocxdrafter/sidecompile.py +107 -0
- texdocxdrafter/stylemap.py +136 -0
- texdocxdrafter/tikz.py +93 -0
- texdocxdrafter/validate.py +228 -0
- texdocxdrafter/writer/__init__.py +1368 -0
- texdocxdrafter/writer/images.py +108 -0
- texdocxdrafter/writer/math.py +171 -0
- texdocxdrafter/writer/numbering.py +117 -0
- texdocxdrafter/writer/textmath.py +222 -0
- texdocxdrafter/writer/xmlutil.py +132 -0
- texdocxdrafter-0.1.2.dist-info/METADATA +463 -0
- texdocxdrafter-0.1.2.dist-info/RECORD +40 -0
- texdocxdrafter-0.1.2.dist-info/WHEEL +5 -0
- texdocxdrafter-0.1.2.dist-info/entry_points.txt +2 -0
- texdocxdrafter-0.1.2.dist-info/licenses/LICENSE +21 -0
- texdocxdrafter-0.1.2.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.1.2"
|
texdocxdrafter/audit.py
ADDED
|
@@ -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()
|