fux-decoder-drawio 4.1.1__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.
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"""`fux_decoder_drawio` — the fux-decoder-drawio package (W-289, SR-PLUGINS).
|
|
2
|
+
|
|
3
|
+
One fux decoder module per format it reads: `fux_decoder_drawio.drawio`. Each is the
|
|
4
|
+
decoder protocol of SR-DECODE — `EXTENSIONS`, `VERSION`, `USES` and
|
|
5
|
+
`decode(raw, rel_path)` — moved here unchanged from `src/fux/decode/` in
|
|
6
|
+
W-289 phase 2.
|
|
7
|
+
|
|
8
|
+
**fux loads a module from here only when `.fux/plugins.toml` declares this
|
|
9
|
+
package**, after verifying its version and digest (SR-PLUGINS P3, P4), by its
|
|
10
|
+
fixed import name (P7). A same-named file in `.fux/decoders/` overrides it
|
|
11
|
+
(P8). This file runs nothing: importing the package imports no decoder.
|
|
12
|
+
"""
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
"""`.drawio` / `.dio` -> Markdown — the labels in an architecture diagram.
|
|
2
|
+
|
|
3
|
+
Niche and worth it: a team's architecture lives in diagrams, and the box labels
|
|
4
|
+
are exactly the terms someone searches for. Today they are invisible.
|
|
5
|
+
|
|
6
|
+
The format is XML, but the interesting part is usually **deflated and
|
|
7
|
+
base64'd** inside a `<diagram>` element — draw.io compresses the model by
|
|
8
|
+
default. `zlib` and `base64` are both stdlib, so this stays `$0`; the only
|
|
9
|
+
subtlety is that the payload is *raw* deflate with no zlib header, which needs
|
|
10
|
+
a negative window size.
|
|
11
|
+
|
|
12
|
+
⚠ **Labels are HTML fragments**, so they are converted through `html` rather
|
|
13
|
+
than stripped by hand — one HTML implementation, per SR-DECODE.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
import base64
|
|
19
|
+
import re
|
|
20
|
+
import zlib
|
|
21
|
+
from urllib.parse import unquote
|
|
22
|
+
from fux.decode.ext.limits import limit
|
|
23
|
+
|
|
24
|
+
# Imports are ABSOLUTE, not relative, and that is what makes this file work in
|
|
25
|
+
# both places it runs: as a package module, and as a consumer copy in
|
|
26
|
+
# `.fux/decoders/` loaded by path. A path-loaded file has no parent package, so
|
|
27
|
+
# `from . import _xml` raises `attempted relative import with no known parent
|
|
28
|
+
# package` — the copy would be dead on arrival. Absolute imports mean the file
|
|
29
|
+
# fux ships and the file you edit are byte-identical (SR-DECODE decision 11).
|
|
30
|
+
from fux.decode.ext import xml as _xml
|
|
31
|
+
from fux_decoder_html.html import html_to_markdown
|
|
32
|
+
from fux.constants import fixed
|
|
33
|
+
|
|
34
|
+
#: **The reuse key's handle on this decoder** (W-166). Bump it by hand in the
|
|
35
|
+
#: same change as any edit that can change what `decode()` returns, and the next
|
|
36
|
+
#: `fux ingest` re-extracts the documents bound to THIS decoder and no others.
|
|
37
|
+
#: Leaving it alone is the claim that the edit cannot move a byte of output.
|
|
38
|
+
#: `tests/decode/test_decoder_versions.py` fails on a changed module that did
|
|
39
|
+
#: not bump it. [SR-DECODE](../../../../records/0139_decode.md) decision 11a.
|
|
40
|
+
VERSION = fixed("decoders.drawio", "version") # not bumped by W-225 4a (caps moved, same values) nor 5b (numerals moved, same output); not bumped by W-288 (imports moved to decode/ext, same output; USES now carries the extensions' VERSIONs)
|
|
41
|
+
|
|
42
|
+
EXTENSIONS = tuple(fixed("decoders.drawio", "extensions"))
|
|
43
|
+
#: The decoder extensions this decoder calls, read by the reuse key (SR-DECODE decision 24).
|
|
44
|
+
USES = tuple(fixed("decoders.drawio", "uses"))
|
|
45
|
+
|
|
46
|
+
#: A diagram page is a complete unit — a short page was being absorbed by
|
|
47
|
+
#: the next one and cited under the wrong name.
|
|
48
|
+
|
|
49
|
+
#: ⚠ **`MAX_INFLATED` is `[limits.drawio] max_inflated` in .fux/formats.toml** since W-225 stage 4a
|
|
50
|
+
#: (SR-LAW-12 decision 9b): read per call through `limit()`, in the extract-config digest.
|
|
51
|
+
_TAG_RE = re.compile(r"<[^>]+>")
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def decode(raw: bytes, rel_path: str) -> str | None:
|
|
55
|
+
try:
|
|
56
|
+
root = _xml.parse(raw)
|
|
57
|
+
except _xml.UnsafeXml:
|
|
58
|
+
return None
|
|
59
|
+
|
|
60
|
+
blocks: list[str] = []
|
|
61
|
+
for diagram in root.iter():
|
|
62
|
+
if _xml.local(diagram.tag) != "diagram":
|
|
63
|
+
continue
|
|
64
|
+
name = ""
|
|
65
|
+
for key, value in diagram.attrib.items():
|
|
66
|
+
if _xml.local(key) == "name":
|
|
67
|
+
name = value
|
|
68
|
+
model = _model(diagram)
|
|
69
|
+
if model is None:
|
|
70
|
+
continue
|
|
71
|
+
labels = _labels(model)
|
|
72
|
+
if not labels:
|
|
73
|
+
continue
|
|
74
|
+
blocks.append(f"## {name}" if name else "## Diagram")
|
|
75
|
+
blocks.extend(f"- {label}" for label in labels)
|
|
76
|
+
|
|
77
|
+
body = "\n\n".join(blocks)
|
|
78
|
+
return body if body.strip() else None
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def _model(diagram):
|
|
82
|
+
"""The `mxGraphModel` element, inflating the compressed payload if needed."""
|
|
83
|
+
for child in diagram:
|
|
84
|
+
if _xml.local(child.tag) == "mxGraphModel":
|
|
85
|
+
return child # stored uncompressed; nothing to do
|
|
86
|
+
payload = (diagram.text or "").strip()
|
|
87
|
+
if not payload:
|
|
88
|
+
return None
|
|
89
|
+
try:
|
|
90
|
+
compressed = base64.b64decode(payload, validate=True)
|
|
91
|
+
# Negative window bits: raw deflate, no zlib header. draw.io writes it
|
|
92
|
+
# this way and a default-window inflate simply fails, which reads as
|
|
93
|
+
# "not a diagram".
|
|
94
|
+
inflated = zlib.decompressobj(-zlib.MAX_WBITS).decompress(
|
|
95
|
+
compressed, limit("drawio", "max_inflated")
|
|
96
|
+
)
|
|
97
|
+
except (ValueError, zlib.error):
|
|
98
|
+
return None
|
|
99
|
+
try:
|
|
100
|
+
return _xml.parse_text(unquote(inflated.decode("utf-8", errors="replace")))
|
|
101
|
+
except _xml.UnsafeXml:
|
|
102
|
+
return None
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def _labels(model) -> list[str]:
|
|
106
|
+
out: list[str] = []
|
|
107
|
+
seen: set[str] = set()
|
|
108
|
+
# Document order, not sorted: a diagram's cells are laid out in a meaningful
|
|
109
|
+
# sequence and `extract.py` scores earlier text slightly higher.
|
|
110
|
+
for cell in model.iter():
|
|
111
|
+
if _xml.local(cell.tag) != "mxCell":
|
|
112
|
+
continue
|
|
113
|
+
value = ""
|
|
114
|
+
for key, attr in cell.attrib.items():
|
|
115
|
+
if _xml.local(key) == "value":
|
|
116
|
+
value = attr
|
|
117
|
+
if not value.strip():
|
|
118
|
+
continue
|
|
119
|
+
text = _plain(value)
|
|
120
|
+
if text and text not in seen:
|
|
121
|
+
seen.add(text)
|
|
122
|
+
out.append(text)
|
|
123
|
+
return out
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def _plain(value: str) -> str:
|
|
127
|
+
if "<" not in value:
|
|
128
|
+
return " ".join(value.split())
|
|
129
|
+
converted = html_to_markdown(value)
|
|
130
|
+
return " ".join(_TAG_RE.sub(" ", converted).split())
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: fux-decoder-drawio
|
|
3
|
+
Version: 4.1.1
|
|
4
|
+
Summary: Fux decoder package: draw.io diagrams.
|
|
5
|
+
Project-URL: Homepage, https://github.com/arpitarya/fux
|
|
6
|
+
Project-URL: Repository, https://github.com/arpitarya/fux
|
|
7
|
+
Author-email: arpit arya <arpitarya.me@gmail.com>
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
Requires-Python: >=3.12
|
|
10
|
+
Requires-Dist: fux-core==4.1.1
|
|
11
|
+
Requires-Dist: fux-decoder-html==4.1.1
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
|
|
14
|
+
# fux-decoder-drawio
|
|
15
|
+
|
|
16
|
+
Part of [fux](https://github.com/arpitarya/fux) — rank your written knowledge
|
|
17
|
+
from a small index committed to git; fetch content from the systems that own
|
|
18
|
+
it; verify freshness at answer time.
|
|
19
|
+
|
|
20
|
+
**`fux-decoder-drawio` is a fux decoder package: draw.io diagrams.** Only `fux-engine` is mandatory;
|
|
21
|
+
everything else is declared per repository in the committed
|
|
22
|
+
`.fux/plugins.toml`, pinned to one version and digest, and refused loudly when
|
|
23
|
+
it is missing or differs, so two machines on one repository never build
|
|
24
|
+
different indexes.
|
|
25
|
+
|
|
26
|
+
It reads `.drawio` `.dio`. Install it and declare it in `.fux/plugins.toml` --
|
|
27
|
+
`fux setup` proposes it when your sources hold those files -- or fux does not
|
|
28
|
+
read them, and `fux doctor` says so.
|
|
29
|
+
|
|
30
|
+
It is released on fux's own version, together with every other fux package.
|
|
31
|
+
Documentation: <https://github.com/arpitarya/fux#readme>
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
fux_decoder_drawio/__init__.py,sha256=Q-K4HIMHkXoehwUQMl1GiqqLuSjDtsfTlWHuSyVt3XM,631
|
|
2
|
+
fux_decoder_drawio/drawio.py,sha256=Ali9ioJJ9XiD-GT303cUsZpmwRFQTFtGqdVmwLtsK_w,5227
|
|
3
|
+
fux_decoder_drawio-4.1.1.dist-info/METADATA,sha256=1eXKxhc7gTUBJhTZ1_ad-y_UWuDlI0lTcJF_PwhF2xE,1310
|
|
4
|
+
fux_decoder_drawio-4.1.1.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
|
|
5
|
+
fux_decoder_drawio-4.1.1.dist-info/RECORD,,
|