@drunkcoding/agents-and-skills 0.0.1
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.
- package/.claude-plugin/marketplace.json +26 -0
- package/LICENSE +21 -0
- package/README.md +50 -0
- package/package.json +33 -0
- package/plugins/tech-graph/.claude-plugin/plugin.json +17 -0
- package/plugins/tech-graph/README.md +66 -0
- package/plugins/tech-graph/agents/tech-graph.md +89 -0
- package/plugins/tech-graph/commands/tech-graph.md +13 -0
- package/plugins/tech-graph/scripts/check-deps.sh +55 -0
- package/plugins/tech-graph/skills/tech-graph/LICENSE +21 -0
- package/plugins/tech-graph/skills/tech-graph/README.md +555 -0
- package/plugins/tech-graph/skills/tech-graph/README.zh.md +514 -0
- package/plugins/tech-graph/skills/tech-graph/SKILL.md +559 -0
- package/plugins/tech-graph/skills/tech-graph/agents/openai.yaml +4 -0
- package/plugins/tech-graph/skills/tech-graph/assets/samples/sample-style1-flat.png +0 -0
- package/plugins/tech-graph/skills/tech-graph/assets/samples/sample-style2-dark.png +0 -0
- package/plugins/tech-graph/skills/tech-graph/assets/samples/sample-style3-blueprint.png +0 -0
- package/plugins/tech-graph/skills/tech-graph/assets/samples/sample-style4-notion.png +0 -0
- package/plugins/tech-graph/skills/tech-graph/assets/samples/sample-style5-glass.png +0 -0
- package/plugins/tech-graph/skills/tech-graph/assets/samples/sample-style6-claude.png +0 -0
- package/plugins/tech-graph/skills/tech-graph/assets/samples/sample-style7-openai.png +0 -0
- package/plugins/tech-graph/skills/tech-graph/fixtures/agent-memory-types-style4.json +181 -0
- package/plugins/tech-graph/skills/tech-graph/fixtures/api-flow-style7.json +40 -0
- package/plugins/tech-graph/skills/tech-graph/fixtures/mem0-style1.json +297 -0
- package/plugins/tech-graph/skills/tech-graph/fixtures/microservices-style3.json +64 -0
- package/plugins/tech-graph/skills/tech-graph/fixtures/multi-agent-style5.json +45 -0
- package/plugins/tech-graph/skills/tech-graph/fixtures/system-architecture-style6.json +48 -0
- package/plugins/tech-graph/skills/tech-graph/fixtures/tool-call-style2.json +182 -0
- package/plugins/tech-graph/skills/tech-graph/package.json +42 -0
- package/plugins/tech-graph/skills/tech-graph/references/icons.md +406 -0
- package/plugins/tech-graph/skills/tech-graph/references/style-1-flat-icon.md +108 -0
- package/plugins/tech-graph/skills/tech-graph/references/style-2-dark-terminal.md +107 -0
- package/plugins/tech-graph/skills/tech-graph/references/style-3-blueprint.md +113 -0
- package/plugins/tech-graph/skills/tech-graph/references/style-4-notion-clean.md +95 -0
- package/plugins/tech-graph/skills/tech-graph/references/style-5-glassmorphism.md +125 -0
- package/plugins/tech-graph/skills/tech-graph/references/style-6-claude-official.md +209 -0
- package/plugins/tech-graph/skills/tech-graph/references/style-7-openai.md +215 -0
- package/plugins/tech-graph/skills/tech-graph/references/style-diagram-matrix.md +135 -0
- package/plugins/tech-graph/skills/tech-graph/references/svg-layout-best-practices.md +100 -0
- package/plugins/tech-graph/skills/tech-graph/scripts/README.md +269 -0
- package/plugins/tech-graph/skills/tech-graph/scripts/generate-diagram.sh +181 -0
- package/plugins/tech-graph/skills/tech-graph/scripts/generate-from-template.py +1587 -0
- package/plugins/tech-graph/skills/tech-graph/scripts/test-all-styles.sh +143 -0
- package/plugins/tech-graph/skills/tech-graph/scripts/validate-svg.sh +306 -0
- package/plugins/tech-graph/skills/tech-graph/templates/agent-architecture.svg +28 -0
- package/plugins/tech-graph/skills/tech-graph/templates/architecture.svg +23 -0
- package/plugins/tech-graph/skills/tech-graph/templates/comparison-matrix.svg +14 -0
- package/plugins/tech-graph/skills/tech-graph/templates/data-flow.svg +28 -0
- package/plugins/tech-graph/skills/tech-graph/templates/er-diagram.svg +21 -0
- package/plugins/tech-graph/skills/tech-graph/templates/flowchart.svg +21 -0
- package/plugins/tech-graph/skills/tech-graph/templates/sequence.svg +20 -0
- package/plugins/tech-graph/skills/tech-graph/templates/state-machine.svg +20 -0
- package/plugins/tech-graph/skills/tech-graph/templates/timeline.svg +19 -0
- package/plugins/tech-graph/skills/tech-graph/templates/use-case.svg +21 -0
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Batch Test Script
|
|
3
|
+
# Renders regression fixtures, validates SVGs, and exports PNGs
|
|
4
|
+
|
|
5
|
+
set -euo pipefail
|
|
6
|
+
|
|
7
|
+
# Colors
|
|
8
|
+
RED='\033[0;31m'
|
|
9
|
+
GREEN='\033[0;32m'
|
|
10
|
+
YELLOW='\033[1;33m'
|
|
11
|
+
BLUE='\033[0;34m'
|
|
12
|
+
NC='\033[0m'
|
|
13
|
+
|
|
14
|
+
SKILL_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
15
|
+
TEST_DIR="${SKILL_DIR}/test-output"
|
|
16
|
+
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
|
|
17
|
+
|
|
18
|
+
echo -e "${BLUE}=== Fireworks Tech Graph - Batch Test ===${NC}"
|
|
19
|
+
echo "Test directory: $TEST_DIR"
|
|
20
|
+
echo "Timestamp: $TIMESTAMP"
|
|
21
|
+
echo ""
|
|
22
|
+
|
|
23
|
+
# Create test directory
|
|
24
|
+
mkdir -p "$TEST_DIR"
|
|
25
|
+
|
|
26
|
+
# Test configuration
|
|
27
|
+
STYLES=(1 2 3 4 5 6 7)
|
|
28
|
+
STYLE_NAMES=("Flat Icon" "Dark Terminal" "Blueprint" "Notion Clean" "Glassmorphism" "Claude Official" "OpenAI Official")
|
|
29
|
+
|
|
30
|
+
# Summary counters
|
|
31
|
+
TOTAL=0
|
|
32
|
+
PASSED=0
|
|
33
|
+
FAILED=0
|
|
34
|
+
|
|
35
|
+
FIXTURES_DIR="${SKILL_DIR}/fixtures"
|
|
36
|
+
|
|
37
|
+
echo -e "${BLUE}Testing all styles...${NC}"
|
|
38
|
+
echo "----------------------------------------"
|
|
39
|
+
|
|
40
|
+
for i in "${!STYLES[@]}"; do
|
|
41
|
+
STYLE="${STYLES[$i]}"
|
|
42
|
+
STYLE_NAME="${STYLE_NAMES[$i]}"
|
|
43
|
+
|
|
44
|
+
echo -e "\n${YELLOW}Style $STYLE: $STYLE_NAME${NC}"
|
|
45
|
+
|
|
46
|
+
# Check if style reference exists
|
|
47
|
+
STYLE_FILE=$(find "${SKILL_DIR}/references" -maxdepth 1 -type f -name "style-${STYLE}-*.md" | head -n 1)
|
|
48
|
+
if [ -z "${STYLE_FILE:-}" ] || [ ! -f "$STYLE_FILE" ]; then
|
|
49
|
+
echo -e "${RED}✗ Style file not found: $STYLE_FILE${NC}"
|
|
50
|
+
FAILED=$((FAILED + 1))
|
|
51
|
+
TOTAL=$((TOTAL + 1))
|
|
52
|
+
continue
|
|
53
|
+
fi
|
|
54
|
+
|
|
55
|
+
echo -e "${GREEN}✓ Style file found${NC}"
|
|
56
|
+
|
|
57
|
+
if [ ! -d "$FIXTURES_DIR" ]; then
|
|
58
|
+
echo -e "${YELLOW}⚠ Fixtures directory not found: $FIXTURES_DIR${NC}"
|
|
59
|
+
continue
|
|
60
|
+
fi
|
|
61
|
+
|
|
62
|
+
FIXTURE_FILES=$(find "$FIXTURES_DIR" -maxdepth 1 -type f -name "*.json" | sort || true)
|
|
63
|
+
MATCHED_FIXTURES=()
|
|
64
|
+
for FIXTURE in $FIXTURE_FILES; do
|
|
65
|
+
FIXTURE_STYLE=$(python3 - "$FIXTURE" <<'PY'
|
|
66
|
+
import json
|
|
67
|
+
import sys
|
|
68
|
+
from pathlib import Path
|
|
69
|
+
data = json.loads(Path(sys.argv[1]).read_text(encoding='utf-8'))
|
|
70
|
+
print(data.get("style", ""))
|
|
71
|
+
PY
|
|
72
|
+
)
|
|
73
|
+
if [ "$FIXTURE_STYLE" = "$STYLE" ]; then
|
|
74
|
+
MATCHED_FIXTURES+=("$FIXTURE")
|
|
75
|
+
fi
|
|
76
|
+
done
|
|
77
|
+
|
|
78
|
+
if [ "${#MATCHED_FIXTURES[@]}" -eq 0 ]; then
|
|
79
|
+
echo -e "${YELLOW}⚠ No regression fixtures found for style $STYLE${NC}"
|
|
80
|
+
continue
|
|
81
|
+
fi
|
|
82
|
+
|
|
83
|
+
# Render, validate, and export each fixture
|
|
84
|
+
for FIXTURE in "${MATCHED_FIXTURES[@]}"; do
|
|
85
|
+
BASENAME=$(basename "$FIXTURE" .json)
|
|
86
|
+
SVG_FILE="${TEST_DIR}/${BASENAME}_${TIMESTAMP}.svg"
|
|
87
|
+
PNG_FILE="${TEST_DIR}/${BASENAME}_${TIMESTAMP}.png"
|
|
88
|
+
TEMPLATE_TYPE=$(python3 - "$FIXTURE" <<'PY'
|
|
89
|
+
import json
|
|
90
|
+
import sys
|
|
91
|
+
from pathlib import Path
|
|
92
|
+
data = json.loads(Path(sys.argv[1]).read_text(encoding='utf-8'))
|
|
93
|
+
print(data.get("template_type", "architecture"))
|
|
94
|
+
PY
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
echo -n " Rendering $BASENAME... "
|
|
98
|
+
TOTAL=$((TOTAL + 1))
|
|
99
|
+
|
|
100
|
+
if python3 "${SKILL_DIR}/scripts/generate-from-template.py" "$TEMPLATE_TYPE" "$SVG_FILE" "$(cat "$FIXTURE")" > /dev/null 2>&1 \
|
|
101
|
+
&& "${SKILL_DIR}/scripts/validate-svg.sh" "$SVG_FILE" > /dev/null 2>&1; then
|
|
102
|
+
PNG_OK=false
|
|
103
|
+
# Prefer cairosvg (best CSS support); fall back to rsvg-convert
|
|
104
|
+
if python3 -c "import cairosvg" 2>/dev/null \
|
|
105
|
+
&& python3 -c "import cairosvg; cairosvg.svg2png(url='${SVG_FILE}', write_to='${PNG_FILE}', scale=2)" 2>/dev/null; then
|
|
106
|
+
PNG_OK=true
|
|
107
|
+
elif command -v rsvg-convert &> /dev/null \
|
|
108
|
+
&& rsvg-convert -w 1920 "$SVG_FILE" -o "$PNG_FILE" 2>/dev/null; then
|
|
109
|
+
PNG_OK=true
|
|
110
|
+
fi
|
|
111
|
+
if [ "$PNG_OK" = true ]; then
|
|
112
|
+
PNG_SIZE=$(du -h "$PNG_FILE" | cut -f1)
|
|
113
|
+
echo -e "${GREEN}✓ Pass${NC} (${PNG_SIZE})"
|
|
114
|
+
else
|
|
115
|
+
echo -e "${GREEN}✓ Pass${NC}"
|
|
116
|
+
fi
|
|
117
|
+
PASSED=$((PASSED + 1))
|
|
118
|
+
else
|
|
119
|
+
echo -e "${RED}✗ Fail${NC}"
|
|
120
|
+
FAILED=$((FAILED + 1))
|
|
121
|
+
if [ -f "$SVG_FILE" ]; then
|
|
122
|
+
"${SKILL_DIR}/scripts/validate-svg.sh" "$SVG_FILE" 2>&1 | grep -E "✗|Error" | sed 's/^/ /' || true
|
|
123
|
+
fi
|
|
124
|
+
fi
|
|
125
|
+
done
|
|
126
|
+
done
|
|
127
|
+
|
|
128
|
+
# Print summary
|
|
129
|
+
echo ""
|
|
130
|
+
echo "========================================"
|
|
131
|
+
echo -e "${BLUE}Test Summary${NC}"
|
|
132
|
+
echo "----------------------------------------"
|
|
133
|
+
echo "Total tests: $TOTAL"
|
|
134
|
+
echo -e "${GREEN}Passed: $PASSED${NC}"
|
|
135
|
+
echo -e "${RED}Failed: $FAILED${NC}"
|
|
136
|
+
|
|
137
|
+
if [ "$FAILED" -eq 0 ]; then
|
|
138
|
+
echo -e "\n${GREEN}✓ All tests passed!${NC}"
|
|
139
|
+
exit 0
|
|
140
|
+
else
|
|
141
|
+
echo -e "\n${RED}✗ Some tests failed${NC}"
|
|
142
|
+
exit 1
|
|
143
|
+
fi
|
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# SVG Validation Script
|
|
3
|
+
# Checks SVG syntax and reports detailed errors
|
|
4
|
+
|
|
5
|
+
set -euo pipefail
|
|
6
|
+
|
|
7
|
+
# Colors for output
|
|
8
|
+
RED='\033[0;31m'
|
|
9
|
+
GREEN='\033[0;32m'
|
|
10
|
+
YELLOW='\033[1;33m'
|
|
11
|
+
NC='\033[0m' # No Color
|
|
12
|
+
|
|
13
|
+
if [ $# -eq 0 ]; then
|
|
14
|
+
echo "Usage: $0 <svg-file>"
|
|
15
|
+
exit 1
|
|
16
|
+
fi
|
|
17
|
+
|
|
18
|
+
SVG_FILE="$1"
|
|
19
|
+
|
|
20
|
+
if [ ! -f "$SVG_FILE" ]; then
|
|
21
|
+
echo -e "${RED}Error: File not found: $SVG_FILE${NC}"
|
|
22
|
+
exit 1
|
|
23
|
+
fi
|
|
24
|
+
|
|
25
|
+
echo "Validating SVG: $SVG_FILE"
|
|
26
|
+
echo "----------------------------------------"
|
|
27
|
+
|
|
28
|
+
FAILURES=0
|
|
29
|
+
|
|
30
|
+
# Check 0: XML syntax
|
|
31
|
+
echo -n "Checking XML syntax... "
|
|
32
|
+
if command -v xmllint &> /dev/null; then
|
|
33
|
+
if xmllint --noout "$SVG_FILE" 2>/dev/null; then
|
|
34
|
+
echo -e "${GREEN}✓ Pass${NC}"
|
|
35
|
+
else
|
|
36
|
+
echo -e "${RED}✗ Fail${NC}"
|
|
37
|
+
xmllint --noout "$SVG_FILE" 2>&1 || true
|
|
38
|
+
FAILURES=$((FAILURES + 1))
|
|
39
|
+
fi
|
|
40
|
+
else
|
|
41
|
+
echo -e "${YELLOW}⚠ Skipped${NC} (xmllint not found)"
|
|
42
|
+
fi
|
|
43
|
+
|
|
44
|
+
# Check 1: Tag balance
|
|
45
|
+
echo -n "Checking tag balance... "
|
|
46
|
+
OPEN_TAGS=$( { grep -o '<[A-Za-z][A-Za-z0-9:-]*' "$SVG_FILE" || true; } | { grep -v '</' || true; } | wc -l | tr -d ' ' )
|
|
47
|
+
SELF_CLOSING=$( { grep -o '/>' "$SVG_FILE" || true; } | wc -l | tr -d ' ' )
|
|
48
|
+
CLOSE_TAGS=$( { grep -o '</[A-Za-z][A-Za-z0-9:-]*>' "$SVG_FILE" || true; } | wc -l | tr -d ' ' )
|
|
49
|
+
TOTAL_CLOSE=$((SELF_CLOSING + CLOSE_TAGS))
|
|
50
|
+
|
|
51
|
+
if [ "$OPEN_TAGS" -eq "$TOTAL_CLOSE" ]; then
|
|
52
|
+
echo -e "${GREEN}✓ Pass${NC} (${OPEN_TAGS} tags)"
|
|
53
|
+
else
|
|
54
|
+
echo -e "${RED}✗ Fail${NC} (${OPEN_TAGS} open, ${TOTAL_CLOSE} close)"
|
|
55
|
+
FAILURES=$((FAILURES + 1))
|
|
56
|
+
fi
|
|
57
|
+
|
|
58
|
+
# Check 2: Quote check
|
|
59
|
+
echo -n "Checking attribute quotes... "
|
|
60
|
+
UNQUOTED=$( { grep -oE '[a-z-]+=[^"'\''> ]' "$SVG_FILE" || true; } | wc -l | tr -d ' ' )
|
|
61
|
+
if [ "$UNQUOTED" -eq 0 ]; then
|
|
62
|
+
echo -e "${GREEN}✓ Pass${NC}"
|
|
63
|
+
else
|
|
64
|
+
echo -e "${RED}✗ Fail${NC} (${UNQUOTED} unquoted attributes)"
|
|
65
|
+
grep -n -oE '[a-z-]+=[^"'\''> ]' "$SVG_FILE" | head -5 || true
|
|
66
|
+
FAILURES=$((FAILURES + 1))
|
|
67
|
+
fi
|
|
68
|
+
|
|
69
|
+
# Check 3: Unescaped entities in text
|
|
70
|
+
echo -n "Checking text entities... "
|
|
71
|
+
SPECIAL=$(python3 - "$SVG_FILE" <<'PY'
|
|
72
|
+
from pathlib import Path
|
|
73
|
+
import re
|
|
74
|
+
import sys
|
|
75
|
+
|
|
76
|
+
text = Path(sys.argv[1]).read_text(encoding='utf-8')
|
|
77
|
+
issues = 0
|
|
78
|
+
for chunk in re.findall(r'>([^<]*)<', text, flags=re.S):
|
|
79
|
+
cleaned = re.sub(r'&(amp|lt|gt|quot|apos);', '', chunk)
|
|
80
|
+
if '&' in cleaned:
|
|
81
|
+
issues += 1
|
|
82
|
+
print(issues)
|
|
83
|
+
PY
|
|
84
|
+
)
|
|
85
|
+
if [ "$SPECIAL" -eq 0 ]; then
|
|
86
|
+
echo -e "${GREEN}✓ Pass${NC}"
|
|
87
|
+
else
|
|
88
|
+
echo -e "${YELLOW}⚠ Warning${NC} (${SPECIAL} potential unescaped entities)"
|
|
89
|
+
fi
|
|
90
|
+
|
|
91
|
+
# Check 4: Marker references
|
|
92
|
+
echo -n "Checking marker references... "
|
|
93
|
+
MARKER_REFS=$( { grep -oE 'marker-end="url\(#[^)]+\)"' "$SVG_FILE" || true; } | { grep -oE '#[^)]+' || true; } | tr -d '#' | sort -u )
|
|
94
|
+
MARKER_DEFS=$( { grep -oE '<marker id="[^"]+"' "$SVG_FILE" || true; } | { grep -oE 'id="[^"]+"' || true; } | tr -d 'id="' | sort -u )
|
|
95
|
+
|
|
96
|
+
MISSING=0
|
|
97
|
+
for ref in $MARKER_REFS; do
|
|
98
|
+
if ! echo "$MARKER_DEFS" | grep -q "^${ref}$"; then
|
|
99
|
+
echo -e "${RED}✗ Missing marker: $ref${NC}"
|
|
100
|
+
MISSING=$((MISSING + 1))
|
|
101
|
+
fi
|
|
102
|
+
done
|
|
103
|
+
|
|
104
|
+
if [ "$MISSING" -eq 0 ]; then
|
|
105
|
+
echo -e "${GREEN}✓ Pass${NC}"
|
|
106
|
+
else
|
|
107
|
+
echo -e "${RED}✗ Fail${NC} (${MISSING} missing markers)"
|
|
108
|
+
FAILURES=$((FAILURES + 1))
|
|
109
|
+
fi
|
|
110
|
+
|
|
111
|
+
# Check 5: Arrow-component collision
|
|
112
|
+
echo -n "Checking arrow collisions... "
|
|
113
|
+
COLLISIONS=$(python3 - "$SVG_FILE" <<'PY'
|
|
114
|
+
from pathlib import Path
|
|
115
|
+
import re
|
|
116
|
+
import sys
|
|
117
|
+
import xml.etree.ElementTree as ET
|
|
118
|
+
|
|
119
|
+
SVG_NS = {'svg': 'http://www.w3.org/2000/svg'}
|
|
120
|
+
|
|
121
|
+
def strip(tag):
|
|
122
|
+
return tag.split('}', 1)[-1]
|
|
123
|
+
|
|
124
|
+
def to_float(value, default=0.0):
|
|
125
|
+
try:
|
|
126
|
+
return float(value)
|
|
127
|
+
except (TypeError, ValueError):
|
|
128
|
+
return default
|
|
129
|
+
|
|
130
|
+
def is_container_rect(el):
|
|
131
|
+
if el.get('stroke-dasharray'):
|
|
132
|
+
return True
|
|
133
|
+
width = to_float(el.get('width'))
|
|
134
|
+
height = to_float(el.get('height'))
|
|
135
|
+
if width > 700 or height > 500:
|
|
136
|
+
return True
|
|
137
|
+
if width < 70 or height < 30:
|
|
138
|
+
return True
|
|
139
|
+
return False
|
|
140
|
+
|
|
141
|
+
def shape_bounds(el):
|
|
142
|
+
tag = strip(el.tag)
|
|
143
|
+
if tag == 'rect':
|
|
144
|
+
if is_container_rect(el):
|
|
145
|
+
return None
|
|
146
|
+
x = to_float(el.get('x'))
|
|
147
|
+
y = to_float(el.get('y'))
|
|
148
|
+
w = to_float(el.get('width'))
|
|
149
|
+
h = to_float(el.get('height'))
|
|
150
|
+
return (x, y, x + w, y + h)
|
|
151
|
+
if tag == 'circle':
|
|
152
|
+
r = to_float(el.get('r'))
|
|
153
|
+
if r < 20:
|
|
154
|
+
return None
|
|
155
|
+
cx = to_float(el.get('cx'))
|
|
156
|
+
cy = to_float(el.get('cy'))
|
|
157
|
+
return (cx - r, cy - r, cx + r, cy + r)
|
|
158
|
+
if tag == 'ellipse':
|
|
159
|
+
rx = to_float(el.get('rx'))
|
|
160
|
+
ry = to_float(el.get('ry'))
|
|
161
|
+
if rx < 20 or ry < 20:
|
|
162
|
+
return None
|
|
163
|
+
cx = to_float(el.get('cx'))
|
|
164
|
+
cy = to_float(el.get('cy'))
|
|
165
|
+
return (cx - rx, cy - ry, cx + rx, cy + ry)
|
|
166
|
+
return None
|
|
167
|
+
|
|
168
|
+
def parse_path_points(d):
|
|
169
|
+
tokens = re.findall(r'[ML]|-?\d+(?:\.\d+)?', d or '')
|
|
170
|
+
if not tokens:
|
|
171
|
+
return []
|
|
172
|
+
points = []
|
|
173
|
+
command = None
|
|
174
|
+
index = 0
|
|
175
|
+
while index < len(tokens):
|
|
176
|
+
token = tokens[index]
|
|
177
|
+
if token in {'M', 'L'}:
|
|
178
|
+
command = token
|
|
179
|
+
index += 1
|
|
180
|
+
continue
|
|
181
|
+
if command not in {'M', 'L'} or index + 1 >= len(tokens):
|
|
182
|
+
return []
|
|
183
|
+
x = float(tokens[index])
|
|
184
|
+
y = float(tokens[index + 1])
|
|
185
|
+
points.append((x, y))
|
|
186
|
+
index += 2
|
|
187
|
+
return points
|
|
188
|
+
|
|
189
|
+
def segment_hits_bounds(p1, p2, bounds):
|
|
190
|
+
x1, y1 = p1
|
|
191
|
+
x2, y2 = p2
|
|
192
|
+
left, top, right, bottom = bounds
|
|
193
|
+
eps = 1e-6
|
|
194
|
+
|
|
195
|
+
if abs(y1 - y2) < eps:
|
|
196
|
+
y = y1
|
|
197
|
+
if not (top + eps < y < bottom - eps):
|
|
198
|
+
return False
|
|
199
|
+
seg_left = min(x1, x2)
|
|
200
|
+
seg_right = max(x1, x2)
|
|
201
|
+
overlap_left = max(seg_left, left)
|
|
202
|
+
overlap_right = min(seg_right, right)
|
|
203
|
+
if overlap_right - overlap_left <= eps:
|
|
204
|
+
return False
|
|
205
|
+
if abs(overlap_left - x1) < eps or abs(overlap_right - x2) < eps:
|
|
206
|
+
return False
|
|
207
|
+
if abs(overlap_left - x2) < eps or abs(overlap_right - x1) < eps:
|
|
208
|
+
return False
|
|
209
|
+
return True
|
|
210
|
+
|
|
211
|
+
if abs(x1 - x2) < eps:
|
|
212
|
+
x = x1
|
|
213
|
+
if not (left + eps < x < right - eps):
|
|
214
|
+
return False
|
|
215
|
+
seg_top = min(y1, y2)
|
|
216
|
+
seg_bottom = max(y1, y2)
|
|
217
|
+
overlap_top = max(seg_top, top)
|
|
218
|
+
overlap_bottom = min(seg_bottom, bottom)
|
|
219
|
+
if overlap_bottom - overlap_top <= eps:
|
|
220
|
+
return False
|
|
221
|
+
if abs(overlap_top - y1) < eps or abs(overlap_bottom - y2) < eps:
|
|
222
|
+
return False
|
|
223
|
+
if abs(overlap_top - y2) < eps or abs(overlap_bottom - y1) < eps:
|
|
224
|
+
return False
|
|
225
|
+
return True
|
|
226
|
+
|
|
227
|
+
return False
|
|
228
|
+
|
|
229
|
+
root = ET.fromstring(Path(sys.argv[1]).read_text(encoding='utf-8'))
|
|
230
|
+
obstacles = [bounds for element in root.iter() if (bounds := shape_bounds(element)) is not None]
|
|
231
|
+
|
|
232
|
+
collisions = 0
|
|
233
|
+
for element in root.iter():
|
|
234
|
+
tag = strip(element.tag)
|
|
235
|
+
if tag == 'line' and element.get('marker-end'):
|
|
236
|
+
points = [
|
|
237
|
+
(to_float(element.get('x1')), to_float(element.get('y1'))),
|
|
238
|
+
(to_float(element.get('x2')), to_float(element.get('y2'))),
|
|
239
|
+
]
|
|
240
|
+
elif tag == 'path' and element.get('marker-end'):
|
|
241
|
+
points = parse_path_points(element.get('d'))
|
|
242
|
+
else:
|
|
243
|
+
continue
|
|
244
|
+
|
|
245
|
+
for p1, p2 in zip(points, points[1:]):
|
|
246
|
+
if any(segment_hits_bounds(p1, p2, bounds) for bounds in obstacles):
|
|
247
|
+
collisions += 1
|
|
248
|
+
break
|
|
249
|
+
|
|
250
|
+
print(collisions)
|
|
251
|
+
PY
|
|
252
|
+
)
|
|
253
|
+
if [ "$COLLISIONS" -eq 0 ]; then
|
|
254
|
+
echo -e "${GREEN}✓ Pass${NC}"
|
|
255
|
+
else
|
|
256
|
+
echo -e "${RED}✗ Fail${NC} (${COLLISIONS} arrow path collision(s))"
|
|
257
|
+
FAILURES=$((FAILURES + 1))
|
|
258
|
+
fi
|
|
259
|
+
|
|
260
|
+
# Check 6: Closing </svg> tag
|
|
261
|
+
echo -n "Checking closing tag... "
|
|
262
|
+
if grep -q '</svg>' "$SVG_FILE"; then
|
|
263
|
+
echo -e "${GREEN}✓ Pass${NC}"
|
|
264
|
+
else
|
|
265
|
+
echo -e "${RED}✗ Fail${NC} (missing </svg>)"
|
|
266
|
+
FAILURES=$((FAILURES + 1))
|
|
267
|
+
fi
|
|
268
|
+
|
|
269
|
+
# Check 7: render validation (cairosvg preferred, rsvg-convert fallback)
|
|
270
|
+
echo -n "Running render validation... "
|
|
271
|
+
RENDER_OK=false
|
|
272
|
+
RENDER_TOOL=""
|
|
273
|
+
RENDER_ERR=""
|
|
274
|
+
|
|
275
|
+
if python3 -c "import cairosvg" 2>/dev/null; then
|
|
276
|
+
RENDER_TOOL="cairosvg"
|
|
277
|
+
if RENDER_ERR=$(python3 -c "import cairosvg; cairosvg.svg2png(url='${SVG_FILE}', write_to='/tmp/test-output.png')" 2>&1); then
|
|
278
|
+
RENDER_OK=true
|
|
279
|
+
fi
|
|
280
|
+
elif command -v rsvg-convert &> /dev/null; then
|
|
281
|
+
RENDER_TOOL="rsvg-convert"
|
|
282
|
+
if RENDER_ERR=$(rsvg-convert "$SVG_FILE" -o /tmp/test-output.png 2>&1); then
|
|
283
|
+
RENDER_OK=true
|
|
284
|
+
fi
|
|
285
|
+
fi
|
|
286
|
+
|
|
287
|
+
if [ "$RENDER_OK" = true ]; then
|
|
288
|
+
echo -e "${GREEN}✓ Pass${NC} (via ${RENDER_TOOL})"
|
|
289
|
+
rm -f /tmp/test-output.png
|
|
290
|
+
elif [ -n "$RENDER_TOOL" ]; then
|
|
291
|
+
echo -e "${RED}✗ Fail${NC} (via ${RENDER_TOOL})"
|
|
292
|
+
echo "${RENDER_TOOL} error:"
|
|
293
|
+
echo "$RENDER_ERR"
|
|
294
|
+
FAILURES=$((FAILURES + 1))
|
|
295
|
+
else
|
|
296
|
+
echo -e "${YELLOW}⚠ Skipped${NC} (no renderer found — install cairosvg: pip install cairosvg)"
|
|
297
|
+
fi
|
|
298
|
+
|
|
299
|
+
echo "----------------------------------------"
|
|
300
|
+
if [ "$FAILURES" -eq 0 ]; then
|
|
301
|
+
echo "Validation complete"
|
|
302
|
+
exit 0
|
|
303
|
+
fi
|
|
304
|
+
|
|
305
|
+
echo -e "${RED}Validation failed (${FAILURES} error(s))${NC}"
|
|
306
|
+
exit 1
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 960 700">
|
|
2
|
+
<defs>
|
|
3
|
+
<marker id="arrowBlue" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
|
|
4
|
+
<polygon points="0 0, 10 3.5, 0 7" fill="#2563eb"/>
|
|
5
|
+
</marker>
|
|
6
|
+
<marker id="arrowPurple" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
|
|
7
|
+
<polygon points="0 0, 10 3.5, 0 7" fill="#7c3aed"/>
|
|
8
|
+
</marker>
|
|
9
|
+
<marker id="arrowGreen" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
|
|
10
|
+
<polygon points="0 0, 10 3.5, 0 7" fill="#059669"/>
|
|
11
|
+
</marker>
|
|
12
|
+
<marker id="arrowOrange" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
|
|
13
|
+
<polygon points="0 0, 10 3.5, 0 7" fill="#ea580c"/>
|
|
14
|
+
</marker>
|
|
15
|
+
</defs>
|
|
16
|
+
<style>
|
|
17
|
+
text { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; }
|
|
18
|
+
.title { font-size: 24px; font-weight: 700; fill: #1e293b; }
|
|
19
|
+
.node-label { font-size: 16px; font-weight: 600; fill: #1e293b; }
|
|
20
|
+
.sub-label { font-size: 13px; font-weight: 400; fill: #64748b; }
|
|
21
|
+
.arrow-label { font-size: 12px; font-weight: 400; fill: #475569; }
|
|
22
|
+
</style>
|
|
23
|
+
<rect width="960" height="700" fill="#ffffff"/>
|
|
24
|
+
<text x="480" y="60" text-anchor="middle" class="title">{{TITLE}}</text>
|
|
25
|
+
<!-- NODES -->
|
|
26
|
+
<!-- ARROWS -->
|
|
27
|
+
<!-- LEGEND -->
|
|
28
|
+
</svg>
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 960 700">
|
|
2
|
+
<defs>
|
|
3
|
+
<marker id="arrowBlue" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
|
|
4
|
+
<polygon points="0 0, 10 3.5, 0 7" fill="#2563eb"/>
|
|
5
|
+
</marker>
|
|
6
|
+
<marker id="arrowGreen" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
|
|
7
|
+
<polygon points="0 0, 10 3.5, 0 7" fill="#059669"/>
|
|
8
|
+
</marker>
|
|
9
|
+
</defs>
|
|
10
|
+
<style>
|
|
11
|
+
text { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; }
|
|
12
|
+
.title { font-size: 24px; font-weight: 700; fill: #1e293b; }
|
|
13
|
+
.layer-label { font-size: 16px; font-weight: 600; fill: #475569; }
|
|
14
|
+
.node-label { font-size: 16px; font-weight: 600; fill: #1e293b; }
|
|
15
|
+
.sub-label { font-size: 13px; font-weight: 400; fill: #64748b; }
|
|
16
|
+
.arrow-label { font-size: 12px; font-weight: 400; fill: #475569; }
|
|
17
|
+
</style>
|
|
18
|
+
<rect width="960" height="700" fill="#ffffff"/>
|
|
19
|
+
<text x="480" y="60" text-anchor="middle" class="title">{{TITLE}}</text>
|
|
20
|
+
<!-- NODES -->
|
|
21
|
+
<!-- ARROWS -->
|
|
22
|
+
<!-- LEGEND -->
|
|
23
|
+
</svg>
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 960 700">
|
|
2
|
+
<style>
|
|
3
|
+
text { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; }
|
|
4
|
+
.title { font-size: 24px; font-weifill: #1e293b; }
|
|
5
|
+
.header { font-size: 14px; font-weight: 600; fill: #1e293b; }
|
|
6
|
+
.row-label { font-size: 13px; font-weight: 500; fill: #475569; }
|
|
7
|
+
.cell-text { font-size: 13px; font-weight: 400; fill: #1e293b; }
|
|
8
|
+
</style>
|
|
9
|
+
<rect width="960" height="700" fill="#ffffff"/>
|
|
10
|
+
<text x="480" y="60" text-anchor="middle" class="title">{{TITLE}}</text>
|
|
11
|
+
<!-- HEADERS -->
|
|
12
|
+
<!-- ROWS -->
|
|
13
|
+
<!-- CELLS -->
|
|
14
|
+
</svg>
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 960 700">
|
|
2
|
+
<defs>
|
|
3
|
+
<marker id="arrowBlue" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
|
|
4
|
+
<polygon points="0 0, 10 3.5, 0 7" fill="#2563eb"/>
|
|
5
|
+
</marker>
|
|
6
|
+
<marker id="arrowPurple" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
|
|
7
|
+
<polygon points="0 0, 10 3.5, 0 7" fill="#7c3aed"/>
|
|
8
|
+
</marker>
|
|
9
|
+
<marker id="arrowGreen" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
|
|
10
|
+
<polygon points="0 0, 10 3.5, 0 7" fill="#059669"/>
|
|
11
|
+
</marker>
|
|
12
|
+
<marker id="arrowOrange" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
|
|
13
|
+
<polygon points="0 0, 10 3.5, 0 7" fill="#ea580c"/>
|
|
14
|
+
</marker>
|
|
15
|
+
</defs>
|
|
16
|
+
<style>
|
|
17
|
+
text { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; }
|
|
18
|
+
.title { font-size: 24px; font-weight: 700; fill: #1e293b; }
|
|
19
|
+
.node-label { font-size: 16px; font-weight: 600; fill: #1e293b; }
|
|
20
|
+
.sub-label { font-size: 13px; font-weight: 400; fill: #64748b; }
|
|
21
|
+
.arrow-label { font-size: 12px; font-weight: 400; fill: #475569; }
|
|
22
|
+
</style>
|
|
23
|
+
<rect width="960" height="700" fill="#ffffff"/>
|
|
24
|
+
<text x="480" y="60" text-anchor="middle" class="title">{{TITLE}}</text>
|
|
25
|
+
<!-- NODES -->
|
|
26
|
+
<!-- ARROWS -->
|
|
27
|
+
<!-- LEGEND -->
|
|
28
|
+
</svg>
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 960 700">
|
|
2
|
+
<defs>
|
|
3
|
+
<marker id="arrowBlue" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
|
|
4
|
+
<polygon points="0 0, 10 3.5, 0 7" fill="#2563eb"/>
|
|
5
|
+
</marker>
|
|
6
|
+
</defs>
|
|
7
|
+
<style>
|
|
8
|
+
text { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; }
|
|
9
|
+
.title { font-size: 24px; font-weight: 700; fill: #1e293b; }
|
|
10
|
+
.entity-name { font-size: 15px; font-weight: 700; fill: #1e293b; }
|
|
11
|
+
.attribute { font-size: 12px; font-weight: 400; fill: #475569; }
|
|
12
|
+
.primary-key { font-size: 12px; font-weight: 600; fill: #1e293b; text-decoration: underline; }
|
|
13
|
+
.relationship { font-size: 12px; font-weight: 500; fill: #64748b; }
|
|
14
|
+
.cardinality { font-size: 11px; font-weight: 400; fill: #64748b; }
|
|
15
|
+
</style>
|
|
16
|
+
<rect width="960" height="700" fill="#ffffff"/>
|
|
17
|
+
<text x="480" y="60" text-anchor="middle" class="title">{{TITLE}}</text>
|
|
18
|
+
<!-- ENTITIES -->
|
|
19
|
+
<!-- RELATIONSHIPS -->
|
|
20
|
+
<!-- CARDINALITY -->
|
|
21
|
+
</svg>
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 960 700">
|
|
2
|
+
<defs>
|
|
3
|
+
<marker id="arrowBlue" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
|
|
4
|
+
<polygon points="0 0, 10 3.5, 0 7" fill="#2563eb"/>
|
|
5
|
+
</marker>
|
|
6
|
+
</defs>
|
|
7
|
+
<style>
|
|
8
|
+
text { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; }
|
|
9
|
+
.title { font-size: 24px; font-weight: 700; fill: #1e293b; }
|
|
10
|
+
.node-label { font-size: 14px; font-weight: 500; fill: #1e293b; }
|
|
11
|
+
.decision-label { font-size: 13px; font-weight: 500; fill: #1e293b; }
|
|
12
|
+
.arrow-label { font-size: 11px; font-weight: 400; fill: #475569; }
|
|
13
|
+
</style>
|
|
14
|
+
<rect width="960" height="700" fill="#ffffff"/>
|
|
15
|
+
<text x="480" y="60" text-anchor="middle" class="title">{{TITLE}}</text>
|
|
16
|
+
<!-- START -->
|
|
17
|
+
<!-- PROCESSES -->
|
|
18
|
+
<!-- DECISIONS -->
|
|
19
|
+
<!-- ARROWS -->
|
|
20
|
+
<!-- END -->
|
|
21
|
+
</svg>
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 960 800">
|
|
2
|
+
<defs>
|
|
3
|
+
<marker id="arrowBlue" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
|
|
4
|
+
<polygon points="0 0, 10 3.5, 0 7" fill="#2563eb"/>
|
|
5
|
+
</marker>
|
|
6
|
+
</defs>
|
|
7
|
+
<style>
|
|
8
|
+
text { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; }
|
|
9
|
+
.title { font-size: 24px; font-weight: 700; fill: #1e293b; }
|
|
10
|
+
.participant { font-size: 14px; font-weight: 600; fill: #1e293b; }
|
|
11
|
+
.message { font-size: 12px; font-weight: 400; fill: #475569; }
|
|
12
|
+
.lifeline { stroke: #cbd5e1; stroke-width: 1.5; stroke-dasharray: 5,3; }
|
|
13
|
+
</style>
|
|
14
|
+
<rect width="960" height="800" fill="#ffffff"/>
|
|
15
|
+
<text x="480" y="60" text-anchor="middle" class="title">{{TITLE}}</text>
|
|
16
|
+
<!-- PARTICIPANTS -->
|
|
17
|
+
<!-- LIFELINES -->
|
|
18
|
+
<!-- MESSAGES -->
|
|
19
|
+
<!-- ACTIVATIONS -->
|
|
20
|
+
</svg>
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 960 700">
|
|
2
|
+
<defs>
|
|
3
|
+
<marker id="arrowBlue" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
|
|
4
|
+
<polygon points="0 0, 10 3.5, 0 7" fill="#2563eb"/>
|
|
5
|
+
</marker>
|
|
6
|
+
</defs>
|
|
7
|
+
<style>
|
|
8
|
+
text { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; }
|
|
9
|
+
.title { font-size: 24px; font-weight: 700; fill: #1e293b; }
|
|
10
|
+
.state-name { font-size: 14px; font-weight: 600; fill: #1e293b; }
|
|
11
|
+
.state-activity { font-size: 11px; font-weight: 400; fill: #64748b; }
|
|
12
|
+
. { font-size: 11px; font-weight: 400; fill: #475569; }
|
|
13
|
+
</style>
|
|
14
|
+
<rect width="960" height="700" fill="#ffffff"/>
|
|
15
|
+
<text x="480" y="60" text-anchor="middle" class="title">{{TITLE}}</text>
|
|
16
|
+
<!-- INITIAL_STATE -->
|
|
17
|
+
<!-- STATES -->
|
|
18
|
+
<!-- TRANSITIONS -->
|
|
19
|
+
<!-- FINAL_STATE -->
|
|
20
|
+
</svg>
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1200 400">
|
|
2
|
+
<defs>
|
|
3
|
+
<marker id="arrowBlue" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
|
|
4
|
+
<polygon points="0 0, 10 3.5, 0 7" fill="#2563eb"/>
|
|
5
|
+
</marker>
|
|
6
|
+
</defs>
|
|
7
|
+
<style>
|
|
8
|
+
text { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; }
|
|
9
|
+
.title { font-size: 24px; font-weight: 700; fill: #1e293b; }
|
|
10
|
+
.period-label { font-size: 12px; font-weight: 500; fill: #475569; }
|
|
11
|
+
.task-label { font-size: 13px; font-weight: 500; fill: #1e293b; }
|
|
12
|
+
.milestone-label { font-size: 12px; font-weight: 600; fill: #1e293b; }
|
|
13
|
+
</style>
|
|
14
|
+
<rect width="1200" height="400" fill="#ffffff"/>
|
|
15
|
+
<text x="600" y="50" text-anchor="middle" class="title">{{TITLE}}</text>
|
|
16
|
+
<!-- TIME_AXIS -->
|
|
17
|
+
<!-- TASKS -->
|
|
18
|
+
<!-- MILESTONES -->
|
|
19
|
+
</svg>
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 960 700">
|
|
2
|
+
<defs>
|
|
3
|
+
<marker id="arrowBlue" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
|
|
4
|
+
<polygon points="0 0, 10 3.5, 0 7" fill="#2563eb"/>
|
|
5
|
+
</marker>
|
|
6
|
+
</defs>
|
|
7
|
+
<style>
|
|
8
|
+
text { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; }
|
|
9
|
+
.title { font-size: 24px; font-weight: 700; fill: #1e293b; }
|
|
10
|
+
.actor-label { font-size: 13px; font-weight: 600; fill: #1e293b; }
|
|
11
|
+
.usecaseel { font-size: 13px; font-weight: 500; fill: #1e293b; }
|
|
12
|
+
.system-label { font-size: 16px; font-weight: 600; fill: #475569; }
|
|
13
|
+
.relationship { font-size: 11px; font-weight: 400; fill: #64748b; font-style: italic; }
|
|
14
|
+
</style>
|
|
15
|
+
<rect width="960" height="700" fill="#ffffff"/>
|
|
16
|
+
<text x="480" y="60" text-anchor="middle" class="title">{{TITLE}}</text>
|
|
17
|
+
<!-- SYSTEM_BOUNDARY -->
|
|
18
|
+
<!-- ACTORS -->
|
|
19
|
+
<!-- USECASES -->
|
|
20
|
+
<!-- RELATIONSHIPS -->
|
|
21
|
+
</svg>
|