mirror of
https://github.com/feigeCode/navop.git
synced 2026-11-06 00:07:34 +00:00
89 lines
3.6 KiB
Python
89 lines
3.6 KiB
Python
import json, subprocess, collections, sys
|
||
from PIL import Image, ImageDraw, ImageFont
|
||
sys.setrecursionlimit(20000)
|
||
|
||
APPS = ['navop','navop-shell','navop-data']
|
||
meta = json.loads(subprocess.run(['cargo','metadata','--no-deps','--format-version','1'],capture_output=True,text=True).stdout)
|
||
ws = {p['name'] for p in meta['packages']}
|
||
|
||
def edges(product):
|
||
out = subprocess.run(['cargo','tree','-p',product,'-e','normal','--prefix','depth'],capture_output=True,text=True).stdout
|
||
stack, es = {}, set()
|
||
for line in out.splitlines():
|
||
if not line.strip(): continue
|
||
d=0
|
||
while d < len(line) and line[d].isdigit(): d+=1
|
||
depth=int(line[:d]) if d else 0
|
||
name=line[d:].strip().split(' v')[0].split(' ')[0]
|
||
stack[depth]=name
|
||
if depth>0 and name in ws and stack.get(depth-1) in ws:
|
||
es.add((stack[depth-1],name))
|
||
return es
|
||
|
||
per={p:edges(p) for p in APPS}
|
||
membership=collections.defaultdict(set)
|
||
for p in APPS:
|
||
for n in {x for e in per[p] for x in e}|{p}:
|
||
membership[n].add(p)
|
||
nodes=set(membership)
|
||
union=set().union(*per.values())
|
||
|
||
preds=collections.defaultdict(set)
|
||
for a,b in union:
|
||
preds[b].add(a)
|
||
|
||
state={}
|
||
def level(n, seen=()):
|
||
if n in state: return state[n]
|
||
if n in seen: return 0
|
||
if n in APPS: state[n]=0; return 0
|
||
ps=preds.get(n,set())
|
||
lv=1+max((level(p, seen+(n,)) for p in ps), default=-1) if ps else 0
|
||
state[n]=max(lv,0); return state[n]
|
||
for n in nodes: level(n)
|
||
|
||
levels=collections.defaultdict(list)
|
||
for n in nodes: levels[state[n]].append(n)
|
||
|
||
def color(n):
|
||
if n in APPS: return (62,74,92), (255,255,255)
|
||
m=membership[n]
|
||
if len(m)==3: return (203,232,203),(20,20,20)
|
||
if m=={'navop','navop-shell'}: return (255,213,160),(20,20,20)
|
||
if m=={'navop','navop-data'}: return (186,214,252),(20,20,20)
|
||
return (228,228,228),(20,20,20)
|
||
|
||
fp='/System/Library/Fonts/Supplemental/Arial Unicode.ttf'
|
||
F=lambda s: ImageFont.truetype(fp,s)
|
||
NW,NH,GX,GY,PAD=152,22,26,10,70
|
||
maxlvl=max(len(v) for v in levels.values())
|
||
W=PAD*2+maxlvl*(NW+GX)
|
||
H=PAD*2+(max(levels)+1)*(NH+GY)+110
|
||
img=Image.new('RGB',(W,H),(255,255,255)); d=ImageDraw.Draw(img)
|
||
mid={}
|
||
order_rank=lambda n:(0 if n in APPS else 1, {'all':0,'shell':1,'data':2,'full':3}[('all' if len(membership[n])==3 else 'shell' if membership[n]=={'navop','navop-shell'} else 'data' if membership[n]=={'navop','navop-data'} else 'full')], n)
|
||
for lv,ns in sorted(levels.items()):
|
||
ns=sorted(ns,key=order_rank)
|
||
tw=len(ns)*(NW+GX)-GX; x0=(W-tw)//2; y=PAD+lv*(NH+GY)
|
||
for i,n in enumerate(ns):
|
||
x=x0+i*(NW+GX); cx=x+NW/2; cy=y+NH/2
|
||
mid[n]=(cx,cy,x,y)
|
||
for a,b in union:
|
||
if a in mid and b in mid:
|
||
ax,ay,_,_=mid[a]; bx,by,_,_=mid[b]
|
||
d.line([(ax,ay+NH/2-3),(bx,by-NH/2+2)],fill=(206,206,206),width=1)
|
||
for n,(cx,cy,x,y) in mid.items():
|
||
fill,fg=color(n)
|
||
d.rounded_rectangle([x,y,x+NW,y+NH],radius=4,fill=fill,outline=(110,110,110))
|
||
d.text((x+5,y+5),n,font=F(12),fill=fg)
|
||
lx,ly=40,H-64
|
||
d.text((lx,ly-22),'navop 三产品依赖图(仅工作区内部 crate;node 颜色 = 出现在哪些产品的 cargo tree 中)',font=F(14),fill=(0,0,0))
|
||
import math
|
||
def swatch(x,y,c): d.rounded_rectangle([x,y,x+24,y+15],radius=3,fill=c,outline=(110,110,110))
|
||
legend=[('应用入口',(62,74,92)),('三产品共有',(203,232,203)),('全量 + 终端(shell)',(255,213,160)),('全量 + 数据(data)',(186,214,252)),('仅全量',(228,228,228))]
|
||
x=lx
|
||
for label,c in legend:
|
||
swatch(x,ly,c); d.text((x+32,ly),label,font=F(13),fill=(0,0,0)); x+=270
|
||
img.save("docs/design/navop-product-dependency-graph.png")
|
||
print('saved',W,H,'nodes',len(nodes),'edges',len(union))
|