40-validation/validate-semantic-ids.py

#!/usr/bin/env python3
import argparse,json,re
from pathlib import Path
ID=re.compile(r"^[A-Za-z0-9][A-Za-z0-9._:-]*$")
def validate(inputs):
 units=[];errors=[]
 for repo,path in inputs:
  try:d=json.loads(path.read_text(encoding="utf-8"))
  except Exception as e:errors.append({"code":"INVALID_MANIFEST","location":f"{repo}:{path}","message":str(e)});continue
  for i,u in enumerate(d.get("semanticUnits",[])):
   loc=f"{repo}:{path}#semanticUnits[{i}]";cid=u.get("id");aliases=u.get("legacyIds",[])
   if not isinstance(cid,str) or not ID.fullmatch(cid):errors.append({"code":"INVALID_ID","id":cid,"location":loc})
   for a in aliases:
    if not isinstance(a,str) or not ID.fullmatch(a):errors.append({"code":"INVALID_ALIAS","id":a,"location":loc})
   units.append((cid,set(aliases),loc))
 canon={}
 for cid,aliases,loc in units:
  canon.setdefault(cid,[]).append(loc)
 for cid,locs in canon.items():
  if len(locs)>1:errors.append({"code":"DUPLICATE_CANONICAL_ID","id":cid,"locations":locs})
 amap={}
 for cid,aliases,loc in units:
  for a in aliases:amap.setdefault(a,[]).append((cid,loc))
 for a,owners in amap.items():
  distinct={x[0] for x in owners}
  if len(distinct)>1:errors.append({"code":"ALIAS_COLLISION","id":a,"locations":[x[1] for x in owners]})
  if a in canon and any(cid!=a for cid,_ in owners):errors.append({"code":"ALIAS_SHADOWS_CANONICAL_ID","id":a,"locations":canon[a]+[x[1] for x in owners]})
 return {"schemaVersion":"1.0","workItem":"SE-0024","status":"PASSED" if not errors else "FAILED","manifests":len(inputs),"semanticUnits":len(units),"canonicalIds":len(canon),"aliases":len(amap),"errors":errors}
def main():
 p=argparse.ArgumentParser();p.add_argument("--input",action="append",required=True,help="repository=path");p.add_argument("--output",type=Path);a=p.parse_args();items=[]
 for x in a.input:
  repo,sep,path=x.partition("=")
  if not sep:p.error("--input benötigt repository=path")
  items.append((repo,Path(path)))
 r=validate(items);text=json.dumps(r,ensure_ascii=False,indent=2)+"\n"
 if a.output:a.output.write_text(text,encoding="utf-8")
 else:print(text,end="")
 raise SystemExit(0 if r["status"]=="PASSED" else 1)
if __name__=="__main__":main()