reverse-skills-max: initial public release (restored, redacted, hardened)
This commit is contained in:
3
reverse-skill-repo/skills/diagram-generator/README.md
Normal file
3
reverse-skill-repo/skills/diagram-generator/README.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# Diagram Generator Skill
|
||||
|
||||
This skill helps AI assistants create editable diagrams in Mermaid, Graphviz DOT, PlantUML, and SVG, and optionally render them with local tools.
|
||||
188
reverse-skill-repo/skills/diagram-generator/SKILL.md
Normal file
188
reverse-skill-repo/skills/diagram-generator/SKILL.md
Normal file
@@ -0,0 +1,188 @@
|
||||
---
|
||||
name: diagram-generator
|
||||
description: generate, refine, validate, and render diagrams from natural language, notes, code snippets, schemas, tables, or existing diagram source. use for flowcharts, swimlanes, sequence diagrams, state diagrams, er diagrams, class diagrams, architecture/c4-style diagrams, dependency graphs, gantt charts, mind maps, user journeys, sankey-style flows, org charts, network graphs, and other visual models. supports mermaid by default, graphviz dot for complex graph layout, plantuml for uml-heavy engineering diagrams, and svg output when direct markup is more reliable.
|
||||
---
|
||||
|
||||
# Diagram Generator
|
||||
|
||||
## ACTION REQUIRED(读完后立刻执行)
|
||||
|
||||
1. `NOW`:确认当前任务是否命中本 skill 的适用范围
|
||||
2. `NOW`:读取 `../tool-index.md`,校验工具可用性和实际路径
|
||||
3. `NEXT`:缺工具时调用 bootstrap,不要猜路径
|
||||
4. `ACT`:进入"工作流"第一步并执行,不要停在确认状态
|
||||
|
||||
## Purpose
|
||||
|
||||
Create clear, editable diagrams from messy or structured inputs. Prefer text-based diagram source first so the result can be reviewed, versioned, and refined. Render to files only when the user asks for an image/PDF or when a downloadable artifact would materially help.
|
||||
|
||||
## Default workflow
|
||||
|
||||
1. Identify the user's intent, audience, and source material.
|
||||
2. Choose the diagram family and language using the decision table below.
|
||||
3. Normalize entities, relationships, labels, states, branches, and time/order information before writing diagram code.
|
||||
4. Generate concise, readable diagram source.
|
||||
5. Validate the syntax mentally and, when creating files, run `scripts/render_diagram.py`.
|
||||
6. Return the diagram source plus a short note about assumptions. When files are generated, include links to the output files.
|
||||
|
||||
Do not over-ask for clarification. If the request is underspecified, make reasonable assumptions and label them briefly.
|
||||
|
||||
## Diagram language decision table
|
||||
|
||||
Use Mermaid unless another language is clearly better.
|
||||
|
||||
| User wants | Prefer | Why |
|
||||
|---|---|---|
|
||||
| process flow, decision tree, simple swimlane | Mermaid flowchart | readable and easy to paste into Markdown |
|
||||
| sequence of system/user interactions | Mermaid sequenceDiagram or PlantUML sequence | Mermaid for docs; PlantUML for UML formality |
|
||||
| lifecycle, state machine, transitions | Mermaid stateDiagram-v2 or PlantUML state | compact transition syntax |
|
||||
| database schema, entities, relationships | Mermaid erDiagram | portable ER notation |
|
||||
| class/interface/object model | Mermaid classDiagram or PlantUML class | Mermaid for docs; PlantUML for detailed UML |
|
||||
| project schedule | Mermaid gantt | concise timeline syntax |
|
||||
| hierarchy, ideas, notes | Mermaid mindmap | good default for idea maps |
|
||||
| customer/product journey | Mermaid journey | built-in journey notation |
|
||||
| git history | Mermaid gitGraph | built-in git notation |
|
||||
| dependency graph, package graph, large network | Graphviz DOT | better layout engines for dense graphs |
|
||||
| architecture with layers, clusters, boundaries | Mermaid flowchart with subgraphs, Graphviz clusters, or PlantUML C4-style | choose based on requested fidelity |
|
||||
| weighted flow/sankey-like relationship | Mermaid sankey-beta when supported, otherwise SVG or Graphviz | Mermaid support may vary by renderer |
|
||||
| custom visual where source languages fit poorly | SVG | precise control over layout and styling |
|
||||
|
||||
## Output policy
|
||||
|
||||
- Always provide editable source unless the user explicitly asks only for an image.
|
||||
- Default to a single best diagram. Offer alternatives only when genuinely useful.
|
||||
- Prefer stable, simple syntax over fancy features that may not render in older Mermaid/PlantUML versions.
|
||||
- Use short labels. Split long text into notes outside the diagram when needed.
|
||||
- Avoid ambiguous node IDs. Use ASCII IDs and human-readable labels.
|
||||
- Preserve user terminology, but standardize capitalization within a diagram.
|
||||
- For technical diagrams, include boundaries such as client, service, database, queue, external API, and operator/user when they are implied.
|
||||
- For business-process diagrams, distinguish happy path, decision points, failures, retries, and manual steps when present.
|
||||
- For diagrams created from uncertain text, include an `Assumptions` section after the code.
|
||||
|
||||
## Mermaid generation rules
|
||||
|
||||
Consult `references/diagram-patterns.md` for compact templates.
|
||||
|
||||
General Mermaid rules:
|
||||
- Start with the correct diagram directive, for example `flowchart TD`, `sequenceDiagram`, `erDiagram`, `gantt`, `mindmap`, or `journey`.
|
||||
- For flowcharts, use `flowchart TD` unless the user asks for left-to-right; use `flowchart LR` for architecture and pipelines.
|
||||
- Use subgraphs for swimlanes or architecture layers. Name subgraphs with readable labels.
|
||||
- Keep node IDs stable and ASCII-only, for example `ingest_service[Ingest Service]`.
|
||||
- Quote labels that contain punctuation likely to confuse the parser.
|
||||
- Use decision diamonds for branching: `decision{Condition?}`.
|
||||
- Use consistent edge labels: `-- yes -->`, `-- no -->`, `-. async .->`, or `== critical ==>` only when meaningful.
|
||||
- In sequence diagrams, declare participants before messages. Use `actor` for humans and `participant` for systems.
|
||||
- Use `alt/else/end`, `opt/end`, `loop/end`, and `par/and/end` blocks for conditional, optional, repeated, and parallel flows.
|
||||
|
||||
## Graphviz DOT generation rules
|
||||
|
||||
Use Graphviz for large, dense, or layout-sensitive relationship diagrams.
|
||||
|
||||
- Prefer `digraph G` for directed relationships and `graph G` for undirected networks.
|
||||
- Set layout-friendly graph attributes at the top: `rankdir=LR`, `nodesep`, `ranksep`, and `splines=true` when helpful.
|
||||
- Use `subgraph cluster_name` for boundaries and subsystems.
|
||||
- Use plain labels and restrained styling.
|
||||
- Use edge labels only when they add meaning.
|
||||
- For many nodes, group by domain with clusters and avoid crossing-heavy all-to-all edges.
|
||||
|
||||
## PlantUML generation rules
|
||||
|
||||
Use PlantUML when the user asks for UML or needs formal UML notation.
|
||||
|
||||
- Wrap diagrams with `@startuml` and `@enduml`.
|
||||
- Use `actor`, `participant`, `database`, `queue`, `collections`, or `component` stereotypes when useful.
|
||||
- Use `package`, `rectangle`, or `node` for architecture boundaries.
|
||||
- For class diagrams, include only important fields/methods unless the user asks for exhaustive detail.
|
||||
- For activity diagrams, use clear start/end markers and explicit branch labels.
|
||||
|
||||
## SVG generation rules
|
||||
|
||||
Use SVG only when text diagram languages cannot express the requested visual reliably.
|
||||
|
||||
- Keep SVG simple, accessible, and editable.
|
||||
- Include `<title>` and meaningful text labels.
|
||||
- Prefer rectangles, lines, arrows, and groups over complex paths.
|
||||
- Do not embed external fonts or remote images.
|
||||
|
||||
## Rendering files
|
||||
|
||||
When the user asks for PNG/SVG/PDF, create a source file and run:
|
||||
|
||||
```bash
|
||||
python "<SKILL_ROOT>/diagram-generator/scripts/render_diagram.py" input.mmd --format svg --out output.svg
|
||||
python "<SKILL_ROOT>/diagram-generator/scripts/render_diagram.py" input.dot --format png --out output.png
|
||||
python "<SKILL_ROOT>/diagram-generator/scripts/render_diagram.py" input.puml --format svg --out output.svg
|
||||
```
|
||||
|
||||
> `<SKILL_ROOT>` 是本包 `skills/` 目录的实际路径,AI 应自动检测。
|
||||
|
||||
The renderer is intentionally dependency-tolerant. It tries common local tools and reports actionable installation hints if a renderer is unavailable. Do not claim an image was rendered unless the script completed successfully and the output file exists.
|
||||
|
||||
## Validation checklist
|
||||
|
||||
Before finalizing:
|
||||
|
||||
- The diagram type matches the user's task.
|
||||
- The source is syntactically plausible for the chosen language.
|
||||
- Labels are short enough to fit.
|
||||
- Edges and message order reflect the input accurately.
|
||||
- Assumptions are called out when the input was incomplete.
|
||||
- For generated files, the output exists and opens or has nonzero size.
|
||||
|
||||
## Common response template
|
||||
|
||||
Use this structure for most diagram answers:
|
||||
|
||||
```markdown
|
||||
下面是可编辑的 [language] 版本:
|
||||
|
||||
```[language]
|
||||
[source]
|
||||
```
|
||||
|
||||
Assumptions:
|
||||
- [only if needed]
|
||||
|
||||
Rendered file: [link] [only if generated]
|
||||
```
|
||||
|
||||
For English user requests, respond in English. For Chinese user requests, respond in Chinese unless they ask otherwise.
|
||||
|
||||
---
|
||||
|
||||
## 按需自举(On-Demand Bootstrap)
|
||||
|
||||
### 自动化能力边界
|
||||
|
||||
| 工具 | 可自动安装 | 安装方式 | 说明 |
|
||||
|------|-----------|---------|------|
|
||||
| Mermaid CLI (mmdc) | ✓ | npm install -g @mermaid-js/mermaid-cli | 渲染 Mermaid 为 PNG/SVG |
|
||||
| Graphviz (dot) | ✗ | 手动安装 | https://graphviz.org/download/ |
|
||||
| PlantUML | ✗ | 需要 Java + plantuml.jar | https://plantuml.com/download |
|
||||
| Python (render script) | ✓ | 已在 bootstrap 中 | `scripts/render_diagram.py` 依赖 |
|
||||
|
||||
### 说明
|
||||
|
||||
本 skill 主要输出文本格式的图表源码(Mermaid/DOT/PlantUML),不一定需要本地渲染工具。只有当用户明确要求生成 PNG/SVG/PDF 文件时才需要对应的渲染器。
|
||||
|
||||
如果渲染器不可用,`scripts/render_diagram.py` 会输出安装提示而不是报错。
|
||||
|
||||
---
|
||||
|
||||
## 路由上下文
|
||||
|
||||
**上游入口**: `skills/SKILL.md`(总控)、`routing.md`
|
||||
**触发条件**: 用户说"画图"、"流程图"、"架构图"、"攻击路径图"、"时序图"、"Mermaid"、"Graphviz"、"PlantUML"
|
||||
**下游出口**:
|
||||
- 生成的图表可嵌入 `docs-generator/` 的报告中
|
||||
- 攻击路径图可配合 `pentest-tools/` 的渗透报告
|
||||
|
||||
**同级关联模块**: `docs-generator/`(报告中嵌入图表)
|
||||
|
||||
|
||||
## 任务完成自检(声称完成前 MUST 通过)
|
||||
|
||||
- [ ] 我是否执行了工作流中的每一步(而不是只阅读)?
|
||||
- [ ] 我是否基于 `tool-index` 使用了真实工具路径?
|
||||
- [ ] 我是否产出了可复现证据(命令/脚本/截图/报告)?
|
||||
- [ ] 我是否完成并回写了 RULES 要求的 Checklist 项?
|
||||
@@ -0,0 +1,3 @@
|
||||
interface:
|
||||
display_name: "Diagram Generator"
|
||||
short_description: "Generate and render Mermaid, Graphviz, PlantUML, and SVG diagrams."
|
||||
@@ -0,0 +1,254 @@
|
||||
# Diagram Patterns
|
||||
|
||||
Use these compact patterns when generating diagram source. Prefer adapting the pattern instead of inventing complex syntax.
|
||||
|
||||
## Mermaid flowchart
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
start([Start]) --> receive[Receive request]
|
||||
receive --> valid{Valid?}
|
||||
valid -- yes --> process[Process request]
|
||||
valid -- no --> fix[Ask for missing info]
|
||||
process --> finish([Finish])
|
||||
```
|
||||
|
||||
## Mermaid swimlane-style flowchart
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
subgraph customer[Customer]
|
||||
c1[Submit order]
|
||||
end
|
||||
subgraph app[Application]
|
||||
a1[Validate order]
|
||||
a2[Create invoice]
|
||||
end
|
||||
subgraph ops[Operations]
|
||||
o1[Review exception]
|
||||
end
|
||||
c1 --> a1
|
||||
a1 -- valid --> a2
|
||||
a1 -- invalid --> o1
|
||||
```
|
||||
|
||||
## Mermaid architecture
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
user[User] --> web[Web App]
|
||||
web --> api[API Service]
|
||||
api --> db[(Database)]
|
||||
api -. async .-> queue[[Queue]]
|
||||
queue --> worker[Worker]
|
||||
worker --> object_store[(Object Store)]
|
||||
```
|
||||
|
||||
## Mermaid sequence diagram
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
actor User
|
||||
participant Web
|
||||
participant API
|
||||
database DB
|
||||
User->>Web: Submit request
|
||||
Web->>API: POST /request
|
||||
API->>DB: Save record
|
||||
DB-->>API: OK
|
||||
API-->>Web: 201 Created
|
||||
Web-->>User: Show confirmation
|
||||
```
|
||||
|
||||
## Mermaid ER diagram
|
||||
|
||||
```mermaid
|
||||
erDiagram
|
||||
CUSTOMER ||--o{ ORDER : places
|
||||
ORDER ||--|{ ORDER_ITEM : contains
|
||||
PRODUCT ||--o{ ORDER_ITEM : appears_in
|
||||
CUSTOMER {
|
||||
string id PK
|
||||
string email
|
||||
}
|
||||
ORDER {
|
||||
string id PK
|
||||
string customer_id FK
|
||||
datetime created_at
|
||||
}
|
||||
```
|
||||
|
||||
## Mermaid state diagram
|
||||
|
||||
```mermaid
|
||||
stateDiagram-v2
|
||||
[*] --> Draft
|
||||
Draft --> Submitted: submit
|
||||
Submitted --> Approved: approve
|
||||
Submitted --> Rejected: reject
|
||||
Approved --> [*]
|
||||
Rejected --> Draft: revise
|
||||
```
|
||||
|
||||
## Mermaid class diagram
|
||||
|
||||
```mermaid
|
||||
classDiagram
|
||||
class User {
|
||||
+string id
|
||||
+string email
|
||||
+login()
|
||||
}
|
||||
class Order {
|
||||
+string id
|
||||
+decimal total
|
||||
+submit()
|
||||
}
|
||||
User "1" --> "0..*" Order : places
|
||||
```
|
||||
|
||||
## Mermaid gantt
|
||||
|
||||
```mermaid
|
||||
gantt
|
||||
title Delivery Plan
|
||||
dateFormat YYYY-MM-DD
|
||||
section Discovery
|
||||
Requirements :a1, 2026-01-01, 5d
|
||||
Design :after a1, 4d
|
||||
section Build
|
||||
Implementation :2026-01-10, 10d
|
||||
QA :2026-01-22, 5d
|
||||
```
|
||||
|
||||
## Mermaid mindmap
|
||||
|
||||
```mermaid
|
||||
mindmap
|
||||
root((Product Launch))
|
||||
Research
|
||||
Customer interviews
|
||||
Market scan
|
||||
Build
|
||||
Prototype
|
||||
QA
|
||||
Go-to-market
|
||||
Pricing
|
||||
Campaign
|
||||
```
|
||||
|
||||
## Mermaid user journey
|
||||
|
||||
```mermaid
|
||||
journey
|
||||
title Trial Signup Journey
|
||||
section Discover
|
||||
Visit landing page: 4: User
|
||||
Compare pricing: 3: User
|
||||
section Activate
|
||||
Create account: 5: User
|
||||
Invite teammate: 4: User
|
||||
```
|
||||
|
||||
## Graphviz dependency graph
|
||||
|
||||
```dot
|
||||
digraph G {
|
||||
rankdir=LR;
|
||||
node [shape=box, style=rounded];
|
||||
app -> api;
|
||||
api -> auth;
|
||||
api -> db;
|
||||
worker -> queue;
|
||||
worker -> db;
|
||||
}
|
||||
```
|
||||
|
||||
## Graphviz clustered architecture
|
||||
|
||||
```dot
|
||||
digraph G {
|
||||
rankdir=LR;
|
||||
compound=true;
|
||||
node [shape=box, style=rounded];
|
||||
|
||||
subgraph cluster_client {
|
||||
label="Client";
|
||||
web;
|
||||
mobile;
|
||||
}
|
||||
|
||||
subgraph cluster_platform {
|
||||
label="Platform";
|
||||
api;
|
||||
worker;
|
||||
queue [shape=cylinder];
|
||||
db [shape=cylinder];
|
||||
}
|
||||
|
||||
web -> api;
|
||||
mobile -> api;
|
||||
api -> db;
|
||||
api -> queue;
|
||||
queue -> worker;
|
||||
worker -> db;
|
||||
}
|
||||
```
|
||||
|
||||
## PlantUML sequence
|
||||
|
||||
```plantuml
|
||||
@startuml
|
||||
actor User
|
||||
participant Web
|
||||
participant API
|
||||
database DB
|
||||
User -> Web: Submit request
|
||||
Web -> API: POST /request
|
||||
API -> DB: Save record
|
||||
DB --> API: OK
|
||||
API --> Web: 201 Created
|
||||
Web --> User: Confirmation
|
||||
@enduml
|
||||
```
|
||||
|
||||
## PlantUML component architecture
|
||||
|
||||
```plantuml
|
||||
@startuml
|
||||
actor User
|
||||
rectangle "Client" {
|
||||
[Web App]
|
||||
}
|
||||
rectangle "Backend" {
|
||||
[API Service]
|
||||
queue "Queue"
|
||||
[Worker]
|
||||
database "Database"
|
||||
}
|
||||
User --> [Web App]
|
||||
[Web App] --> [API Service]
|
||||
[API Service] --> Database
|
||||
[API Service] --> Queue
|
||||
Queue --> [Worker]
|
||||
[Worker] --> Database
|
||||
@enduml
|
||||
```
|
||||
|
||||
## SVG fallback
|
||||
|
||||
```svg
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="900" height="360" viewBox="0 0 900 360" role="img">
|
||||
<title>Simple process diagram</title>
|
||||
<defs>
|
||||
<marker id="arrow" markerWidth="10" markerHeight="10" refX="8" refY="3" orient="auto">
|
||||
<path d="M0,0 L0,6 L9,3 z" />
|
||||
</marker>
|
||||
</defs>
|
||||
<rect x="40" y="120" width="160" height="70" rx="10" fill="white" stroke="black" />
|
||||
<text x="120" y="160" text-anchor="middle">Start</text>
|
||||
<line x1="200" y1="155" x2="320" y2="155" stroke="black" marker-end="url(#arrow)" />
|
||||
<rect x="320" y="120" width="180" height="70" rx="10" fill="white" stroke="black" />
|
||||
<text x="410" y="160" text-anchor="middle">Process</text>
|
||||
</svg>
|
||||
```
|
||||
@@ -0,0 +1,25 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Create sample diagram sources for smoke testing the diagram-generator skill."""
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
SAMPLES = {
|
||||
"sample_flow.mmd": """flowchart TD\n start([Start]) --> validate{Valid?}\n validate -- yes --> done([Done])\n validate -- no --> fix[Fix input]\n fix --> validate\n""",
|
||||
"sample_graph.dot": """digraph G {\n rankdir=LR;\n node [shape=box, style=rounded];\n app -> api;\n api -> db;\n}\n""",
|
||||
"sample_sequence.puml": """@startuml\nactor User\nparticipant API\nUser -> API: Request\nAPI --> User: Response\n@enduml\n""",
|
||||
"sample.svg": """<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"200\" height=\"80\" role=\"img\"><title>Sample</title><rect x=\"10\" y=\"10\" width=\"180\" height=\"60\" fill=\"white\" stroke=\"black\"/><text x=\"100\" y=\"45\" text-anchor=\"middle\">Sample</text></svg>\n""",
|
||||
}
|
||||
|
||||
|
||||
def main() -> int:
|
||||
out_dir = Path.cwd() / "samples"
|
||||
out_dir.mkdir(exist_ok=True)
|
||||
for name, content in SAMPLES.items():
|
||||
path = out_dir / name
|
||||
path.write_text(content, encoding="utf-8")
|
||||
print(path)
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -0,0 +1,169 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Render Mermaid, Graphviz DOT, PlantUML, or SVG diagram source to an output file.
|
||||
|
||||
The script is intentionally dependency-tolerant. It uses common command-line
|
||||
renderers when available and prints actionable hints otherwise.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
MERMAID_EXTS = {".mmd", ".mermaid"}
|
||||
GRAPHVIZ_EXTS = {".dot", ".gv"}
|
||||
PLANTUML_EXTS = {".puml", ".plantuml"}
|
||||
SVG_EXTS = {".svg"}
|
||||
|
||||
|
||||
def run(cmd: list[str], cwd: Path | None = None) -> None:
|
||||
print("$ " + " ".join(cmd))
|
||||
completed = subprocess.run(cmd, cwd=str(cwd) if cwd else None, text=True, capture_output=True)
|
||||
if completed.stdout:
|
||||
print(completed.stdout, end="")
|
||||
if completed.stderr:
|
||||
print(completed.stderr, end="", file=sys.stderr)
|
||||
if completed.returncode != 0:
|
||||
raise RuntimeError(f"command failed with exit code {completed.returncode}")
|
||||
|
||||
|
||||
def infer_kind(path: Path) -> str:
|
||||
ext = path.suffix.lower()
|
||||
if ext in MERMAID_EXTS:
|
||||
return "mermaid"
|
||||
if ext in GRAPHVIZ_EXTS:
|
||||
return "graphviz"
|
||||
if ext in PLANTUML_EXTS:
|
||||
return "plantuml"
|
||||
if ext in SVG_EXTS:
|
||||
return "svg"
|
||||
text = path.read_text(encoding="utf-8", errors="replace").lstrip()
|
||||
if text.startswith("@startuml"):
|
||||
return "plantuml"
|
||||
if text.startswith("digraph") or text.startswith("graph"):
|
||||
return "graphviz"
|
||||
if text.startswith("<svg"):
|
||||
return "svg"
|
||||
return "mermaid"
|
||||
|
||||
|
||||
def render_mermaid(src: Path, out: Path, fmt: str) -> None:
|
||||
mmdc = shutil.which("mmdc")
|
||||
if not mmdc:
|
||||
raise RuntimeError(
|
||||
"Mermaid rendering requires the Mermaid CLI executable `mmdc`. "
|
||||
"Install it with `npm install -g @mermaid-js/mermaid-cli`, or return the Mermaid source only."
|
||||
)
|
||||
run([mmdc, "-i", str(src), "-o", str(out)])
|
||||
|
||||
|
||||
def render_graphviz(src: Path, out: Path, fmt: str) -> None:
|
||||
dot = shutil.which("dot")
|
||||
if not dot:
|
||||
raise RuntimeError(
|
||||
"Graphviz rendering requires the `dot` executable. Install Graphviz, or return the DOT source only."
|
||||
)
|
||||
run([dot, f"-T{fmt}", str(src), "-o", str(out)])
|
||||
|
||||
|
||||
def render_plantuml(src: Path, out: Path, fmt: str) -> None:
|
||||
plantuml = shutil.which("plantuml")
|
||||
java = shutil.which("java")
|
||||
if plantuml:
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
tmp_path = Path(tmp)
|
||||
temp_src = tmp_path / src.name
|
||||
temp_src.write_text(src.read_text(encoding="utf-8"), encoding="utf-8")
|
||||
run([plantuml, f"-t{fmt}", str(temp_src)], cwd=tmp_path)
|
||||
produced = temp_src.with_suffix("." + fmt)
|
||||
if not produced.exists():
|
||||
candidates = list(tmp_path.glob(f"*.{fmt}"))
|
||||
if not candidates:
|
||||
raise RuntimeError("PlantUML completed but no output file was found")
|
||||
produced = candidates[0]
|
||||
shutil.copyfile(produced, out)
|
||||
return
|
||||
jar = os.environ.get("PLANTUML_JAR")
|
||||
if java and jar and Path(jar).exists():
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
tmp_path = Path(tmp)
|
||||
temp_src = tmp_path / src.name
|
||||
temp_src.write_text(src.read_text(encoding="utf-8"), encoding="utf-8")
|
||||
run([java, "-jar", jar, f"-t{fmt}", str(temp_src)], cwd=tmp_path)
|
||||
produced = temp_src.with_suffix("." + fmt)
|
||||
if not produced.exists():
|
||||
candidates = list(tmp_path.glob(f"*.{fmt}"))
|
||||
if not candidates:
|
||||
raise RuntimeError("PlantUML completed but no output file was found")
|
||||
produced = candidates[0]
|
||||
shutil.copyfile(produced, out)
|
||||
return
|
||||
raise RuntimeError(
|
||||
"PlantUML rendering requires `plantuml` on PATH, or Java plus PLANTUML_JAR pointing to plantuml.jar. "
|
||||
"Return the PlantUML source only if no renderer is available."
|
||||
)
|
||||
|
||||
|
||||
def render_svg_source(src: Path, out: Path, fmt: str) -> None:
|
||||
if fmt == "svg":
|
||||
shutil.copyfile(src, out)
|
||||
return
|
||||
rsvg = shutil.which("rsvg-convert")
|
||||
if rsvg:
|
||||
run([rsvg, "-f", fmt, "-o", str(out), str(src)])
|
||||
return
|
||||
inkscape = shutil.which("inkscape")
|
||||
if inkscape:
|
||||
run([inkscape, str(src), f"--export-type={fmt}", f"--export-filename={out}"])
|
||||
return
|
||||
raise RuntimeError(
|
||||
"SVG conversion requires `rsvg-convert` or `inkscape`. The SVG source itself is usable as an image."
|
||||
)
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description="Render text diagram source to SVG, PNG, or PDF.")
|
||||
parser.add_argument("input", type=Path, help="Input .mmd, .dot/.gv, .puml, or .svg file")
|
||||
parser.add_argument("--format", choices=["svg", "png", "pdf"], default="svg", help="Output format")
|
||||
parser.add_argument("--out", type=Path, help="Output path. Defaults to input name with the selected extension")
|
||||
parser.add_argument("--kind", choices=["auto", "mermaid", "graphviz", "plantuml", "svg"], default="auto")
|
||||
args = parser.parse_args()
|
||||
|
||||
src = args.input.resolve()
|
||||
if not src.exists():
|
||||
print(f"input file not found: {src}", file=sys.stderr)
|
||||
return 2
|
||||
out = (args.out or src.with_suffix("." + args.format)).resolve()
|
||||
out.parent.mkdir(parents=True, exist_ok=True)
|
||||
kind = infer_kind(src) if args.kind == "auto" else args.kind
|
||||
|
||||
try:
|
||||
if kind == "mermaid":
|
||||
render_mermaid(src, out, args.format)
|
||||
elif kind == "graphviz":
|
||||
render_graphviz(src, out, args.format)
|
||||
elif kind == "plantuml":
|
||||
render_plantuml(src, out, args.format)
|
||||
elif kind == "svg":
|
||||
render_svg_source(src, out, args.format)
|
||||
else:
|
||||
raise RuntimeError(f"unknown diagram kind: {kind}")
|
||||
except Exception as exc:
|
||||
print(f"render failed: {exc}", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
if not out.exists() or out.stat().st_size == 0:
|
||||
print(f"render failed: output missing or empty: {out}", file=sys.stderr)
|
||||
return 1
|
||||
print(f"rendered {kind} diagram to {out}")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in New Issue
Block a user