drawio-python 0.1.0__py3-none-any.whl → 0.1.4__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.
- drawio/VERSION +1 -0
- drawio/__init__.py +5 -0
- drawio/__main__.py +4 -4
- drawio/cli.py +393 -22
- drawio/doctor.py +144 -0
- drawio/document.py +985 -0
- drawio/entities/__init__.py +4 -0
- drawio/entities/base_entity.py +9 -0
- drawio/entities/config.py +1 -2
- drawio/entities/drawio.py +4 -2
- drawio/entities/drawio_app.py +3 -9
- drawio/entities/drawio_browser.py +174 -28
- drawio/entities/drawio_config/abstract_config.py +2 -2
- drawio/entities/drawio_config/drawio_config.py +13 -1
- drawio/entities/drawio_config/label_config.py +5 -0
- drawio/entities/erd/column.py +2 -1
- drawio/entities/erd/erd.py +45 -0
- drawio/entities/erd/table.py +3 -3
- drawio/entities/graph/__init__.py +4 -0
- drawio/entities/graph/edge.py +18 -15
- drawio/entities/graph/graph.py +130 -117
- drawio/entities/graph/node.py +63 -62
- drawio/entities/logger.py +8 -0
- drawio/entities/renderers/__init__.py +7 -0
- drawio/entities/renderers/base_render.py +24 -0
- drawio/entities/renderers/drawio_render.py +16 -0
- drawio/entities/renderers/pdf_render.py +59 -0
- drawio/itergrations/__init__.py +0 -0
- drawio/itergrations/mermaid/__init__.py +0 -0
- drawio/itergrations/mermaid/mermaid_controller.py +108 -0
- drawio/layout.py +283 -0
- drawio/lint.py +531 -0
- drawio/render/__init__.py +223 -0
- drawio/render/browser.py +169 -0
- drawio/render/desktop.py +104 -0
- drawio/render/fonts.py +239 -0
- drawio/render/native.py +638 -0
- drawio/scene.py +658 -0
- drawio/shapes.py +82 -0
- drawio/skill/SKILL.md +155 -0
- drawio/skill/references/api.md +101 -0
- drawio/skill.py +40 -0
- drawio/text.py +127 -0
- drawio_python-0.1.4.dist-info/METADATA +286 -0
- drawio_python-0.1.4.dist-info/RECORD +54 -0
- {drawio_python-0.1.0.dist-info → drawio_python-0.1.4.dist-info}/WHEEL +1 -1
- drawio_python-0.1.0.dist-info/METADATA +0 -33
- drawio_python-0.1.0.dist-info/RECORD +0 -28
- {drawio_python-0.1.0.dist-info → drawio_python-0.1.4.dist-info}/entry_points.txt +0 -0
- {drawio_python-0.1.0.dist-info → drawio_python-0.1.4.dist-info/licenses}/LICENSE +0 -0
- {drawio_python-0.1.0.dist-info → drawio_python-0.1.4.dist-info}/top_level.txt +0 -0
drawio/VERSION
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
0.1.4
|
drawio/__init__.py
CHANGED
drawio/__main__.py
CHANGED
drawio/cli.py
CHANGED
|
@@ -1,28 +1,399 @@
|
|
|
1
|
-
"""
|
|
1
|
+
"""Offline utilities for editable draw.io files.
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
- Install click or typer and create a CLI app
|
|
6
|
-
- Use builtin argparse
|
|
7
|
-
- Start a web application
|
|
8
|
-
- Import things from your .base module
|
|
3
|
+
Every command that reports something accepts --json, so an agent can read the result
|
|
4
|
+
without parsing prose. Exit status: 0 success, 1 failure or findings, 2 bad usage.
|
|
9
5
|
"""
|
|
10
6
|
|
|
7
|
+
import argparse
|
|
8
|
+
import json
|
|
9
|
+
import sys
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
|
|
12
|
+
from drawio.document import Document
|
|
13
|
+
|
|
14
|
+
_VERSION = Path(__file__).with_name("VERSION").read_text(encoding="utf-8").strip()
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _emit(data, as_json: bool, text: str) -> None:
|
|
18
|
+
print(json.dumps(data, indent=2, ensure_ascii=False) if as_json else text)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _pages_info(document):
|
|
22
|
+
return [
|
|
23
|
+
{
|
|
24
|
+
"id": page.id,
|
|
25
|
+
"name": page.name,
|
|
26
|
+
"shapes": sum(c.element.get("vertex") == "1" for c in page.cells),
|
|
27
|
+
"connectors": sum(c.element.get("edge") == "1" for c in page.cells),
|
|
28
|
+
}
|
|
29
|
+
for page in document.pages
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _cmd_new(args):
|
|
34
|
+
document = Document()
|
|
35
|
+
document.add_page(args.name)
|
|
36
|
+
print(document.save(args.output, overwrite=args.overwrite))
|
|
37
|
+
return 0
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _cmd_validate(args):
|
|
41
|
+
document = Document.load(args.input)
|
|
42
|
+
document.validate(check_bounds=args.check_bounds)
|
|
43
|
+
_emit(
|
|
44
|
+
{"valid": True, "pages": len(document.pages)},
|
|
45
|
+
args.json,
|
|
46
|
+
f"Valid: {len(document.pages)} page(s)",
|
|
47
|
+
)
|
|
48
|
+
return 0
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def _cmd_info(args):
|
|
52
|
+
print(json.dumps(_pages_info(Document.load(args.input)), indent=2, ensure_ascii=False))
|
|
53
|
+
return 0
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _cmd_url(args):
|
|
57
|
+
print(Document.load(args.input).to_url())
|
|
58
|
+
return 0
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _cmd_render(args):
|
|
62
|
+
from drawio.render import render, render_all
|
|
63
|
+
|
|
64
|
+
source = Path(args.input)
|
|
65
|
+
options = dict(
|
|
66
|
+
scale=args.scale,
|
|
67
|
+
border=args.border,
|
|
68
|
+
backend=args.backend,
|
|
69
|
+
aspect=args.aspect,
|
|
70
|
+
transparent=args.transparent,
|
|
71
|
+
download=not args.offline,
|
|
72
|
+
)
|
|
73
|
+
if args.all_pages:
|
|
74
|
+
fmt = (args.format or "png").lower()
|
|
75
|
+
folder = Path(args.output) if args.output else source.with_suffix("")
|
|
76
|
+
results = render_all(source, folder, fmt=fmt, **options)
|
|
77
|
+
else:
|
|
78
|
+
output = args.output or str(source.with_suffix("." + (args.format or "png").lower()))
|
|
79
|
+
results = [render(source, output, page=args.page, fmt=args.format, **options)]
|
|
80
|
+
data = [
|
|
81
|
+
{"output": str(r.path), "backend": r.backend, "page": r.page, "notes": r.notes}
|
|
82
|
+
for r in results
|
|
83
|
+
]
|
|
84
|
+
lines = []
|
|
85
|
+
for r in results:
|
|
86
|
+
lines.append(f"{r.path} (page {r.page}, {r.backend} backend)")
|
|
87
|
+
lines += [f" note: {n}" for n in r.notes]
|
|
88
|
+
_emit(data, args.json, "\n".join(lines))
|
|
89
|
+
return 0
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def _cmd_lint(args):
|
|
93
|
+
from drawio.lint import SEVERITIES, check, parse_fit
|
|
94
|
+
|
|
95
|
+
document = Document.load(args.input)
|
|
96
|
+
document.validate()
|
|
97
|
+
fit = parse_fit(args.fit, args.min_pt) if args.fit else None
|
|
98
|
+
issues = check(document, fit=fit, pages=args.page or None)
|
|
99
|
+
threshold = SEVERITIES.index(args.fail_on)
|
|
100
|
+
failing = [i for i in issues if SEVERITIES.index(i.severity) <= threshold]
|
|
101
|
+
counts = {s: sum(i.severity == s for i in issues) for s in SEVERITIES}
|
|
102
|
+
if args.json:
|
|
103
|
+
print(
|
|
104
|
+
json.dumps(
|
|
105
|
+
{"file": args.input, "summary": counts, "issues": [i.to_dict() for i in issues]},
|
|
106
|
+
indent=2,
|
|
107
|
+
ensure_ascii=False,
|
|
108
|
+
)
|
|
109
|
+
)
|
|
110
|
+
else:
|
|
111
|
+
for issue in issues:
|
|
112
|
+
print(f"{issue.severity}: [{issue.code}] page {issue.page}: {issue.message}")
|
|
113
|
+
if issue.hint:
|
|
114
|
+
print(f" fix: {issue.hint}")
|
|
115
|
+
summary = ", ".join(f"{n} {s}(s)" for s, n in counts.items() if n)
|
|
116
|
+
print(f"{args.input}: {summary or 'no layout problems found'}")
|
|
117
|
+
return 1 if failing else 0
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def _cmd_inspect(args):
|
|
121
|
+
from drawio.scene import build
|
|
122
|
+
|
|
123
|
+
document = Document.load(args.input)
|
|
124
|
+
pages = []
|
|
125
|
+
for page in document.pages:
|
|
126
|
+
if args.page and args.page not in (page.id, page.name):
|
|
127
|
+
continue
|
|
128
|
+
scene = build(page)
|
|
129
|
+
shapes = [
|
|
130
|
+
{
|
|
131
|
+
"id": s.id,
|
|
132
|
+
"label": s.label.text if s.label else "",
|
|
133
|
+
"kind": s.kind,
|
|
134
|
+
"box": [round(v, 1) for v in s.box],
|
|
135
|
+
"parent": s.parent if s.parent not in ("0", "1") else None,
|
|
136
|
+
"container": s.container,
|
|
137
|
+
}
|
|
138
|
+
for s in scene.shapes
|
|
139
|
+
]
|
|
140
|
+
connectors = [
|
|
141
|
+
{
|
|
142
|
+
"id": e.id,
|
|
143
|
+
"source": e.source,
|
|
144
|
+
"target": e.target,
|
|
145
|
+
"label": " / ".join(label.text for label in e.labels),
|
|
146
|
+
"route": [[round(x, 1), round(y, 1)] for x, y in e.points],
|
|
147
|
+
}
|
|
148
|
+
for e in scene.connectors
|
|
149
|
+
]
|
|
150
|
+
bounds = [round(v, 1) for v in scene.bounds()]
|
|
151
|
+
pages.append(
|
|
152
|
+
{
|
|
153
|
+
"id": page.id,
|
|
154
|
+
"name": page.name,
|
|
155
|
+
"bounds": bounds,
|
|
156
|
+
"shapes": shapes,
|
|
157
|
+
"connectors": connectors,
|
|
158
|
+
}
|
|
159
|
+
)
|
|
160
|
+
if args.json:
|
|
161
|
+
print(json.dumps(pages, indent=2, ensure_ascii=False))
|
|
162
|
+
return 0
|
|
163
|
+
for page in pages:
|
|
164
|
+
print(f"page {page['id']} {page['name']!r} bounds x,y,w,h={page['bounds']}")
|
|
165
|
+
for s in page["shapes"]:
|
|
166
|
+
parent = f" in {s['parent']}" if s["parent"] else ""
|
|
167
|
+
label = " ".join(s["label"].split())
|
|
168
|
+
print(f" shape {s['id']:<12} {s['kind']:<10} {s['box']}{parent} {label[:60]!r}")
|
|
169
|
+
for e in page["connectors"]:
|
|
170
|
+
label = f" {e['label']!r}" if e["label"] else ""
|
|
171
|
+
print(f" edge {e['id']:<12} {e['source']} -> {e['target']}{label}")
|
|
172
|
+
return 0
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def _write(document, args) -> Path:
|
|
176
|
+
target = args.output or args.input
|
|
177
|
+
return document.save(target, overwrite=args.output is None or args.overwrite)
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def _cmd_fit(args):
|
|
181
|
+
document = Document.load(args.input)
|
|
182
|
+
resized = []
|
|
183
|
+
for page in document.pages:
|
|
184
|
+
if args.autosize:
|
|
185
|
+
from drawio.lint import check_scene
|
|
186
|
+
from drawio.scene import build
|
|
187
|
+
|
|
188
|
+
for issue in check_scene(build(page)):
|
|
189
|
+
if issue.code == "text-overflow":
|
|
190
|
+
page.autosize(issue.cells[0], max_width=args.max_width)
|
|
191
|
+
resized.append(issue.cells[0])
|
|
192
|
+
page.fit(padding=args.padding)
|
|
193
|
+
path = _write(document, args)
|
|
194
|
+
_emit({"output": str(path), "autosized": resized}, args.json, f"{path}")
|
|
195
|
+
return 0
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
def _cmd_layout(args):
|
|
199
|
+
from drawio.layout import layered
|
|
200
|
+
|
|
201
|
+
document = Document.load(args.input)
|
|
202
|
+
pages = [p for p in document.pages if not args.page or args.page in (p.id, p.name)]
|
|
203
|
+
if not pages:
|
|
204
|
+
raise ValueError(f"No page with ID or name {args.page!r}")
|
|
205
|
+
for page in pages:
|
|
206
|
+
layered(page, direction=args.direction, rank_gap=args.rank_gap, gap=args.gap)
|
|
207
|
+
page.fit()
|
|
208
|
+
path = _write(document, args)
|
|
209
|
+
_emit({"output": str(path), "pages": [p.id for p in pages]}, args.json, f"{path}")
|
|
210
|
+
return 0
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
def _cmd_merge(args):
|
|
214
|
+
merged = Document()
|
|
215
|
+
for name in args.inputs:
|
|
216
|
+
for page in Document.load(name).pages:
|
|
217
|
+
merged.import_page(page)
|
|
218
|
+
path = merged.save(args.output, overwrite=args.overwrite)
|
|
219
|
+
_emit({"output": str(path), "pages": _pages_info(merged)}, args.json, f"{path}")
|
|
220
|
+
return 0
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
def _cmd_shapes(args):
|
|
224
|
+
from drawio.shapes import ARROWS, PALETTE, SHAPES
|
|
225
|
+
|
|
226
|
+
data = {"shapes": SHAPES, "colors": PALETTE, "arrows": list(ARROWS)}
|
|
227
|
+
text = [
|
|
228
|
+
"shapes (page.box(..., shape=NAME)): " + ", ".join(SHAPES),
|
|
229
|
+
"colors (style=drawio.shapes.color(NAME)): " + ", ".join(PALETTE),
|
|
230
|
+
"arrows (page.connect(..., arrow=NAME)): " + ", ".join(ARROWS),
|
|
231
|
+
]
|
|
232
|
+
_emit(data, args.json, "\n".join(text))
|
|
233
|
+
return 0
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
def _cmd_doctor(args):
|
|
237
|
+
from drawio.doctor import diagnose
|
|
238
|
+
|
|
239
|
+
report = diagnose()
|
|
240
|
+
if args.json:
|
|
241
|
+
print(json.dumps(report, indent=2, ensure_ascii=False))
|
|
242
|
+
return 0
|
|
243
|
+
for item in report["checks"]:
|
|
244
|
+
mark = "ok " if item["ok"] else "-- "
|
|
245
|
+
print(f"{mark} {item['name']}: {item['detail']}")
|
|
246
|
+
if not item["ok"] and item.get("fix"):
|
|
247
|
+
print(f" fix: {item['fix']}")
|
|
248
|
+
print("render backends available: " + ", ".join(report["backends"]))
|
|
249
|
+
return 0
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
def _cmd_skill(args):
|
|
253
|
+
from drawio.skill import install, skill_dir
|
|
254
|
+
|
|
255
|
+
if args.action == "path":
|
|
256
|
+
print(skill_dir())
|
|
257
|
+
return 0
|
|
258
|
+
if args.action == "show":
|
|
259
|
+
print((skill_dir() / "SKILL.md").read_text(encoding="utf-8"))
|
|
260
|
+
return 0
|
|
261
|
+
written = install(args.agent, dest=args.dest, force=args.force)
|
|
262
|
+
_emit([str(p) for p in written], args.json, "\n".join(f"installed {p}" for p in written))
|
|
263
|
+
return 0
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
def _parser():
|
|
267
|
+
parser = argparse.ArgumentParser(
|
|
268
|
+
prog="drawio",
|
|
269
|
+
description="Build, check and export editable draw.io diagrams. "
|
|
270
|
+
"Run `drawio skill show` for the agent workflow.",
|
|
271
|
+
)
|
|
272
|
+
parser.add_argument("--version", action="version", version=_VERSION)
|
|
273
|
+
commands = parser.add_subparsers(dest="command", required=True)
|
|
274
|
+
|
|
275
|
+
def add(name, func, help):
|
|
276
|
+
sub = commands.add_parser(name, help=help, description=help)
|
|
277
|
+
sub.set_defaults(func=func)
|
|
278
|
+
return sub
|
|
279
|
+
|
|
280
|
+
def json_flag(sub):
|
|
281
|
+
sub.add_argument("--json", action="store_true", help="Print machine-readable JSON")
|
|
282
|
+
|
|
283
|
+
def output_flags(sub):
|
|
284
|
+
sub.add_argument("-o", "--output", help="Write here instead of updating the input")
|
|
285
|
+
sub.add_argument("--overwrite", action="store_true", help="Replace an existing output")
|
|
286
|
+
|
|
287
|
+
new = add("new", _cmd_new, "Create an empty one-page document")
|
|
288
|
+
new.add_argument("output", type=Path)
|
|
289
|
+
new.add_argument("--name", default="Overview")
|
|
290
|
+
new.add_argument("--overwrite", action="store_true")
|
|
291
|
+
|
|
292
|
+
validate = add("validate", _cmd_validate, "Check IDs, references and parent hierarchy")
|
|
293
|
+
validate.add_argument("input", type=Path)
|
|
294
|
+
validate.add_argument(
|
|
295
|
+
"--check-bounds", action="store_true", help="Also reject children outside containers"
|
|
296
|
+
)
|
|
297
|
+
json_flag(validate)
|
|
298
|
+
|
|
299
|
+
info = add("info", _cmd_info, "Print page IDs, names and cell counts as JSON")
|
|
300
|
+
info.add_argument("input", type=Path)
|
|
301
|
+
|
|
302
|
+
url = add("url", _cmd_url, "Encode file contents in an editor URL (no upload)")
|
|
303
|
+
url.add_argument("input", type=Path)
|
|
304
|
+
|
|
305
|
+
render = add("render", _cmd_render, "Export a page to PNG, SVG, PDF or JPEG")
|
|
306
|
+
render.add_argument("input")
|
|
307
|
+
render.add_argument("-o", "--output", help="Output file (or folder with --all-pages)")
|
|
308
|
+
render.add_argument("-f", "--format", choices=["png", "svg", "pdf", "jpg", "jpeg"])
|
|
309
|
+
render.add_argument("-p", "--page", help="Page index (0-based), ID or name; default first")
|
|
310
|
+
render.add_argument("--all-pages", action="store_true", help="One file per page")
|
|
311
|
+
render.add_argument("-s", "--scale", type=float, default=1)
|
|
312
|
+
render.add_argument("--border", type=int, default=10)
|
|
313
|
+
render.add_argument(
|
|
314
|
+
"--backend",
|
|
315
|
+
choices=["auto", "native", "desktop", "browser"],
|
|
316
|
+
default="auto",
|
|
317
|
+
help="auto: draw.io desktop if installed, else native (default)",
|
|
318
|
+
)
|
|
319
|
+
render.add_argument("--aspect", help="Pad the canvas to a ratio such as 16:9 (native)")
|
|
320
|
+
render.add_argument("--transparent", action="store_true", help="No background (PNG)")
|
|
321
|
+
render.add_argument(
|
|
322
|
+
"--offline", action="store_true", help="Never download the diagrams.net viewer"
|
|
323
|
+
)
|
|
324
|
+
json_flag(render)
|
|
325
|
+
|
|
326
|
+
lint = add("lint", _cmd_lint, "Find overflowing text, overlaps and crossing connectors")
|
|
327
|
+
lint.add_argument("input")
|
|
328
|
+
lint.add_argument("-p", "--page", action="append", help="Only these page IDs or names")
|
|
329
|
+
lint.add_argument(
|
|
330
|
+
"--fit",
|
|
331
|
+
help="Size the picture will be shown at, e.g. 8.15x4.85in, 16cm, 1200px; reports text"
|
|
332
|
+
" smaller than --min-pt",
|
|
333
|
+
)
|
|
334
|
+
lint.add_argument("--min-pt", type=float, default=12)
|
|
335
|
+
lint.add_argument(
|
|
336
|
+
"--fail-on",
|
|
337
|
+
choices=["error", "warning", "info"],
|
|
338
|
+
default="warning",
|
|
339
|
+
help="Lowest severity that makes the exit status 1 (default warning)",
|
|
340
|
+
)
|
|
341
|
+
json_flag(lint)
|
|
342
|
+
|
|
343
|
+
inspect = add("inspect", _cmd_inspect, "List shapes and connectors with page coordinates")
|
|
344
|
+
inspect.add_argument("input")
|
|
345
|
+
inspect.add_argument("-p", "--page", help="Only this page ID or name")
|
|
346
|
+
json_flag(inspect)
|
|
347
|
+
|
|
348
|
+
fit = add("fit", _cmd_fit, "Grow containers and the page to fit their contents")
|
|
349
|
+
fit.add_argument("input")
|
|
350
|
+
output_flags(fit)
|
|
351
|
+
fit.add_argument("--padding", type=float, default=20)
|
|
352
|
+
fit.add_argument(
|
|
353
|
+
"--autosize", action="store_true", help="Also grow shapes whose text overflows"
|
|
354
|
+
)
|
|
355
|
+
fit.add_argument("--max-width", type=float, help="Widest a shape may grow with --autosize")
|
|
356
|
+
json_flag(fit)
|
|
357
|
+
|
|
358
|
+
layout = add("layout", _cmd_layout, "Arrange top-level shapes in ranks along connectors")
|
|
359
|
+
layout.add_argument("input")
|
|
360
|
+
output_flags(layout)
|
|
361
|
+
layout.add_argument("-p", "--page", help="Only this page ID or name")
|
|
362
|
+
layout.add_argument("--direction", choices=["down", "up", "right", "left"], default="down")
|
|
363
|
+
layout.add_argument("--rank-gap", type=float, default=80)
|
|
364
|
+
layout.add_argument("--gap", type=float, default=40)
|
|
365
|
+
json_flag(layout)
|
|
366
|
+
|
|
367
|
+
merge = add("merge", _cmd_merge, "Combine the pages of several files into one")
|
|
368
|
+
merge.add_argument("output")
|
|
369
|
+
merge.add_argument("inputs", nargs="+")
|
|
370
|
+
merge.add_argument("--overwrite", action="store_true")
|
|
371
|
+
json_flag(merge)
|
|
372
|
+
|
|
373
|
+
shapes = add("shapes", _cmd_shapes, "List shape names, colour presets and arrow ends")
|
|
374
|
+
json_flag(shapes)
|
|
375
|
+
|
|
376
|
+
doctor = add("doctor", _cmd_doctor, "Report which render backends and fonts are available")
|
|
377
|
+
json_flag(doctor)
|
|
11
378
|
|
|
12
|
-
|
|
13
|
-
"""
|
|
14
|
-
|
|
15
|
-
|
|
379
|
+
skill = add("skill", _cmd_skill, "Install or print the agent skill shipped with drawio")
|
|
380
|
+
skill.add_argument("action", choices=["install", "show", "path"])
|
|
381
|
+
skill.add_argument(
|
|
382
|
+
"--agent",
|
|
383
|
+
choices=["claude", "codex", "agents", "all"],
|
|
384
|
+
default="all",
|
|
385
|
+
help="Where to install: ~/.claude/skills, ~/.codex/skills, ~/.agents/skills",
|
|
386
|
+
)
|
|
387
|
+
skill.add_argument("--dest", help="Install into this skills folder instead")
|
|
388
|
+
skill.add_argument("--force", action="store_true", help="Replace an installed copy")
|
|
389
|
+
json_flag(skill)
|
|
390
|
+
return parser
|
|
16
391
|
|
|
17
|
-
This is your program's entry point.
|
|
18
392
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
* Run an application (Flask, FastAPI, Django, etc.)
|
|
27
|
-
"""
|
|
28
|
-
print("This will do something")
|
|
393
|
+
def main(argv=None):
|
|
394
|
+
args = _parser().parse_args(argv)
|
|
395
|
+
try:
|
|
396
|
+
return args.func(args)
|
|
397
|
+
except (OSError, ValueError, TypeError, RuntimeError) as exc:
|
|
398
|
+
print(f"drawio: {exc}", file=sys.stderr)
|
|
399
|
+
return 1
|
drawio/doctor.py
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
"""What this machine can do with drawio-python, and how to enable what is missing."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import importlib.util
|
|
6
|
+
import os
|
|
7
|
+
import platform
|
|
8
|
+
import shutil
|
|
9
|
+
import sys
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
|
|
12
|
+
_CHROME = {
|
|
13
|
+
"darwin": [
|
|
14
|
+
"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
|
|
15
|
+
"/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge",
|
|
16
|
+
"/Applications/Chromium.app/Contents/MacOS/Chromium",
|
|
17
|
+
],
|
|
18
|
+
"win32": [
|
|
19
|
+
os.path.join(
|
|
20
|
+
os.environ.get("ProgramFiles", r"C:\Program Files"),
|
|
21
|
+
r"Google\Chrome\Application\chrome.exe",
|
|
22
|
+
),
|
|
23
|
+
os.path.join(
|
|
24
|
+
os.environ.get("ProgramFiles(x86)", r"C:\Program Files (x86)"),
|
|
25
|
+
r"Microsoft\Edge\Application\msedge.exe",
|
|
26
|
+
),
|
|
27
|
+
],
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _has(module: str) -> bool:
|
|
32
|
+
return importlib.util.find_spec(module) is not None
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _system_browser():
|
|
36
|
+
env = os.environ.get("DRAWIO_CHROME")
|
|
37
|
+
if env:
|
|
38
|
+
return env
|
|
39
|
+
key = "win32" if sys.platform.startswith("win") else sys.platform
|
|
40
|
+
for path in _CHROME.get(key, []):
|
|
41
|
+
if Path(path).is_file():
|
|
42
|
+
return path
|
|
43
|
+
for name in (
|
|
44
|
+
"google-chrome",
|
|
45
|
+
"google-chrome-stable",
|
|
46
|
+
"chromium",
|
|
47
|
+
"chromium-browser",
|
|
48
|
+
"microsoft-edge",
|
|
49
|
+
"msedge",
|
|
50
|
+
"chrome",
|
|
51
|
+
):
|
|
52
|
+
found = shutil.which(name)
|
|
53
|
+
if found:
|
|
54
|
+
return found
|
|
55
|
+
return None
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def _playwright_chromium():
|
|
59
|
+
if not _has("playwright"):
|
|
60
|
+
return None
|
|
61
|
+
try:
|
|
62
|
+
from playwright.sync_api import sync_playwright
|
|
63
|
+
|
|
64
|
+
with sync_playwright() as p:
|
|
65
|
+
path = p.chromium.executable_path
|
|
66
|
+
return path if path and Path(path).exists() else None
|
|
67
|
+
except Exception:
|
|
68
|
+
return None
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def diagnose() -> dict:
|
|
72
|
+
from . import __file__ as package_file
|
|
73
|
+
from .render import browser, desktop
|
|
74
|
+
from .render.fonts import font_path
|
|
75
|
+
|
|
76
|
+
version = Path(package_file).with_name("VERSION").read_text(encoding="utf-8").strip()
|
|
77
|
+
pillow = _has("PIL")
|
|
78
|
+
vector_pdf = _has("cairosvg") or bool(shutil.which("rsvg-convert"))
|
|
79
|
+
desktop_path = desktop.find()
|
|
80
|
+
has_playwright = _has("playwright")
|
|
81
|
+
chromium = _playwright_chromium() if has_playwright else None
|
|
82
|
+
system_browser = _system_browser()
|
|
83
|
+
viewer = browser.viewer_script(download=False)
|
|
84
|
+
font = font_path("Helvetica") if pillow else None
|
|
85
|
+
checks = [
|
|
86
|
+
{
|
|
87
|
+
"name": "drawio-python",
|
|
88
|
+
"ok": True,
|
|
89
|
+
"detail": f"{version} at {Path(package_file).parent} (Python {platform.python_version()})",
|
|
90
|
+
},
|
|
91
|
+
{"name": "native SVG", "ok": True, "detail": "always available, no dependencies"},
|
|
92
|
+
{
|
|
93
|
+
"name": "Pillow (PNG, JPEG, PDF)",
|
|
94
|
+
"ok": pillow,
|
|
95
|
+
"detail": "installed" if pillow else "missing",
|
|
96
|
+
"fix": "pip install 'drawio-python[render]'",
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
"name": "vector PDF converter",
|
|
100
|
+
"ok": vector_pdf,
|
|
101
|
+
"detail": "cairosvg or rsvg-convert found"
|
|
102
|
+
if vector_pdf
|
|
103
|
+
else "none; native PDF output is a raster image",
|
|
104
|
+
"fix": "pip install cairosvg, or install librsvg (rsvg-convert)",
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
"name": "raster font",
|
|
108
|
+
"ok": bool(font),
|
|
109
|
+
"detail": font or "Pillow's built-in font",
|
|
110
|
+
"fix": "install Arial, Liberation Sans, DejaVu Sans or Noto Sans, or set DRAWIO_FONT",
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
"name": "draw.io desktop",
|
|
114
|
+
"ok": bool(desktop_path),
|
|
115
|
+
"detail": desktop_path or "not found",
|
|
116
|
+
"fix": "https://github.com/jgraph/drawio-desktop/releases (or set DRAWIO_DESKTOP)",
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
"name": "Playwright",
|
|
120
|
+
"ok": has_playwright,
|
|
121
|
+
"detail": "installed" if has_playwright else "missing",
|
|
122
|
+
"fix": "pip install 'drawio-python[browser-render]'",
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
"name": "browser for --backend browser",
|
|
126
|
+
"ok": bool(chromium or system_browser),
|
|
127
|
+
"detail": chromium or system_browser or "none",
|
|
128
|
+
"fix": "playwright install chromium, or install Chrome/Edge, or set DRAWIO_CHROME",
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
"name": "diagrams.net viewer script",
|
|
132
|
+
"ok": bool(viewer),
|
|
133
|
+
"detail": str(viewer) if viewer else "not cached; downloaded on first browser render",
|
|
134
|
+
"fix": f"allow one download from {browser.VIEWER_URL} or set DRAWIO_VIEWER_JS",
|
|
135
|
+
},
|
|
136
|
+
]
|
|
137
|
+
backends = ["native (svg)"]
|
|
138
|
+
if pillow:
|
|
139
|
+
backends.append("native (png, jpg, pdf)")
|
|
140
|
+
if desktop_path:
|
|
141
|
+
backends.append("desktop")
|
|
142
|
+
if has_playwright and (chromium or system_browser):
|
|
143
|
+
backends.append("browser")
|
|
144
|
+
return {"version": version, "checks": checks, "backends": backends}
|