#!/usr/bin/env python3 """Build the frozen Unicode 15.0.0 Latin canonical decomposition case list.""" import argparse,hashlib,json,pathlib,unicodedata p=argparse.ArgumentParser();p.add_argument('--unicode-data',type=pathlib.Path,required=True);p.add_argument('--output',type=pathlib.Path,required=True);a=p.parse_args() if unicodedata.unidata_version!='15.0.0':raise SystemExit('Case generation requires Python unicodedata 15.0.0; shaping reproduction uses the frozen cases.json and has no such requirement.') b=a.unicode_data.read_bytes();cases=[] for row in (line.split(';') for line in b.decode().splitlines()): cp=int(row[0],16);c=chr(cp) if row[1].startswith('LATIN ') and row[2].startswith('L') and row[5] and not row[5].startswith('<'): nfd=unicodedata.normalize('NFD',c) if len(nfd)>1 and unicodedata.normalize('NFC',nfd)==c:cases.append({'id':f'U+{cp:04X}','kind':'canonical-latin','name':row[1],'nfc':c,'nfd':nfd}) for label,s in [('q-acute','q\u0301'),('Q-acute','Q\u0301'),('q-dotbelow-dotabove','q\u0323\u0307'),('x-acute','x\u0301')]: assert unicodedata.normalize('NFC',s)==s;cases.append({'id':label,'kind':'noncomposable-control','name':label,'nfc':s,'nfd':s}) a.output.write_text(json.dumps({'source':'https://www.unicode.org/Public/15.0.0/ucd/UnicodeData.txt','sourceSha256':hashlib.sha256(b).hexdigest(),'unicodeVersion':'15.0.0','construction':'Latin-named letters with canonical decomposition, NFD length > 1, and NFC(NFD(c)) == c, plus four explicit noncomposable controls','cases':cases},ensure_ascii=False,indent=2)+'\n') print(f'{len(cases)} cases written')