72 lines
3.7 KiB
Python
72 lines
3.7 KiB
Python
"""Compare reference CW descriptors with the repository's compiled descriptor set."""
|
|
import argparse
|
|
import json
|
|
import re
|
|
from pathlib import Path
|
|
from google.protobuf import descriptor_pb2 as d
|
|
|
|
|
|
def messages(files):
|
|
result = {}
|
|
def visit(rows, prefix):
|
|
for row in rows:
|
|
full = prefix+'.'+row.name
|
|
result[full] = row
|
|
visit(row.nested_type, full)
|
|
for file in files:
|
|
visit(file.message_type, '.'+file.package)
|
|
return result
|
|
|
|
|
|
def main():
|
|
p = argparse.ArgumentParser(description=__doc__)
|
|
p.add_argument('--reference', type=Path, required=True)
|
|
p.add_argument('--current', type=Path, required=True)
|
|
p.add_argument('--commands', type=Path, required=True)
|
|
p.add_argument('--output', type=Path, required=True)
|
|
args = p.parse_args()
|
|
reference = d.FileDescriptorProto.FromString(args.reference.read_bytes())
|
|
current = d.FileDescriptorSet.FromString(args.current.read_bytes())
|
|
fm, cm = messages([reference]), messages(current.file)
|
|
current_names = {int(v):k for k,v in re.findall(r'"([^"]+)":\s*(\d+)',args.commands.read_text())}
|
|
commands = [{'id':v.number,'ffgo_name':v.name[3:],'current_name':current_names.get(v.number)} for enum in reference.enum_type if enum.name == 'CmdGridFightType' for v in enum.value if v.number]
|
|
mapping, conflicts = {}, []
|
|
def match(a,b,seen):
|
|
if (a,b) in seen:
|
|
return True
|
|
seen.add((a,b))
|
|
if a not in fm or b not in cm:
|
|
return False
|
|
xf, yf = ({v.number:v for v in rows.field} for rows in (fm[a],cm[b]))
|
|
if xf.keys()!=yf.keys():
|
|
return False
|
|
for num,u in xf.items():
|
|
v=yf[num]
|
|
if u.type!=v.type or u.label!=v.label or u.HasField('oneof_index')!=v.HasField('oneof_index'):
|
|
return False
|
|
if u.type==u.TYPE_MESSAGE and not match(u.type_name,v.type_name,seen):
|
|
return False
|
|
# Preserve the partition of fields into oneofs, independent of names.
|
|
groups = lambda fields: sorted(sorted(v.number for v in fields if v.HasField('oneof_index') and v.oneof_index==i) for i in set(v.oneof_index for v in fields if v.HasField('oneof_index')))
|
|
return groups(fm[a].field)==groups(cm[b].field)
|
|
for row in commands:
|
|
row['same_name']=row['ffgo_name']==row['current_name']
|
|
a='.'+reference.package+'.'+row['ffgo_name']
|
|
candidates=[k for k in cm if k.rsplit('.',1)[-1]==row['current_name']]
|
|
seen=set()
|
|
row['wire_shape_equal']=len(candidates)==1 and match(a,candidates[0],seen)
|
|
if row['wire_shape_equal']:
|
|
for k,v in seen:
|
|
if k in mapping and mapping[k]!=v:
|
|
conflicts.append([k,mapping[k],v])
|
|
mapping[k]=v
|
|
args.output.mkdir(parents=True,exist_ok=True)
|
|
provenance={'reference_descriptor_sha256':__import__('hashlib').sha256(args.reference.read_bytes()).hexdigest(),'current_descriptor_sha256':__import__('hashlib').sha256(args.current.read_bytes()).hexdigest(),'limitations':['Equal wire shapes do not establish enum meanings, field semantics, runtime success, or permission to register numeric aliases.']}
|
|
(args.output/'cw-command-comparison.json').write_text(json.dumps({'provenance':provenance,'commands':commands},indent=2))
|
|
(args.output/'cw-message-mapping.json').write_text(json.dumps({'provenance':provenance,'messages':mapping,'conflicts':conflicts},indent=2))
|
|
print(json.dumps({'commands':len(commands),'same_names':sum(x['same_name'] for x in commands),'equal_wire_shapes':sum(x['wire_shape_equal'] for x in commands),'mapped_messages':len(mapping),'conflicts':len(conflicts),'shape_differences':[x for x in commands if not x['wire_shape_equal']]}))
|
|
|
|
|
|
if __name__=='__main__':
|
|
main()
|